如何复合函数并持久化进度

How to composite functions and persist the progress

我有一系列异步方法定义如下:

func step1(input: Step1InputData, completion: (Step1OutputData -> Void)) { /* do something */ }
func step2(input: Step1OutputData, completion: (Step2OutputData -> Void)) { /* do something */ }
// etc...

如你所见,step1的输出是step2的输入。这些类型都实现了 StepData 协议:

protocol StepData {}
class Step1InputData : StepData { }
class Step1OutputData : StepData { }
class Step2OutputData : StepData { }

最后,我有了这个自定义运算符:

infix operator => { associativity left }

func => <P:StepData, Q:StepData, R:StepData> (left:((P, Q -> Void) -> Void), right:((Q, R -> Void) -> Void)) -> ((P, R -> Void) -> Void) {
    return { (x, completion) in
        left(x, { y in
            right(y, completion)
        })
    }
}

...这意味着我可以写以下内容:

let combinedStep = step1 => step2

这很棒,因为它具有超强的可读性,至关重要的是它在步骤之间强制执行类型安全。

问题是我希望能够坚持一个combinedStep的进度。例如,step1 可能是图片上传,step2 可能是本地存储写入。如果 step2 失败,下次我尝试时,我想从我离开的地方继续,而不是重新上传图像。当然,可以有任意数量的步骤链接在一起。

可以想出一个系统,该系统具有一系列步骤并管理步骤之间的数据传递,并且能够保持进度,但是,我不能'不要想办法在保持编译时类型安全的同时做到这一点。

有更多函数式编程经验的人可以为我指明正确的方向吗?

我建议您研究 PromiseKit, BrightFutures, and ReactiveCocoa.

这样的框架

那些框架和你有同样的问题和解决方案space,需要组合的异步操作,它们都提供了多种处理错误和重试的方法。

如果您喜欢其中的任何一个,您可以采用它们来代替您的自定义实现,并利用大型开发人员社区的支持。或者你可以从他们的代码中找到灵感,带回你的。

尽情享受

我认为你需要让你的运算符正确关联并在完成处理程序中传递一个枚举以允许失败。

enum StepResult<Output> {
    case Success(Output)
    case Failure((StepResult<Output>->Void)->Void)

    static func toFailHandlerWithInput<Input>(input: Input, processor: (Input,(StepResult<Output>)->Void)->Void) -> StepResult<Output> {
        let handler : ((StepResult<Output>->Void)->Void) = { completion in
            processor(input) { pout in
                completion(pout)
            }
        }
        return .Failure(handler)
    }
}


infix operator => { associativity right }

func =><Input,Intermediate,Output>(left: (Input,(StepResult<Intermediate>)->Void)->Void, right: (Intermediate,(StepResult<Output>)->Void)->Void) -> (Input,(StepResult<Output>)->Void)->Void {
    var mergedOp : ((Input,(StepResult<Output>)->Void)->Void)!

    mergedOp = { input,completion in
        left(input) { intermediate in
            switch intermediate {
            case .Success(let output):
                right(output, completion)
            case .Failure:
                let failure = StepResult.toFailHandlerWithInput(input, processor: mergedOp)
                completion(failure)
            }
        }
    }
    return mergedOp
}


var counter1 = 0
func step1(input: Int, completion: (StepResult<Double>)->Void) {
    print("performing step 1...")
    counter1 += 1
    if ( counter1 > 1 ) {
        print("Step 1 will succeed...")
        let result = 2.0 * Double(input + counter1)
        completion(.Success(result))
    } else {
        print("Step 1 fails...")
        completion(StepResult.toFailHandlerWithInput(input, processor: step1))
    }
}


var counter2 = 0
func step2(input: Double, completion: (StepResult<Double>)->Void) {
    print("performing Step 2...")
    counter2 += 1
    if ( counter2 > 2 ) {
        print("Step 2 will succeed...")
        let result = 3 * input + Double(counter2)
        completion(.Success(result))
    } else {
        print("Step 2 fails...")
        completion(StepResult.toFailHandlerWithInput(input, processor: step2))
    }
}


var counter3 = 0
func step3(input: Double, completion: (StepResult<Double>)->Void) {
    print("performing Step 3...")
    counter3 += 1
    if ( counter3 > 1 ) {
        print("Step 3 will succeed...")
        let result = 4 * input + Double(counter3)
        completion(.Success(result))
    } else {
        print("Step 3 fails...")
        completion(StepResult.toFailHandlerWithInput(input, processor: step3))
    }
}


func comboHandler(result: StepResult<Double>) {
    switch result {
    case .Success(let output):
        print("output: \(output)")
    case .Failure(let failHandler):
        failHandler(comboHandler)   // call again until success
    }
}


let combinedSteps = step1 => step2 => step3
combinedSteps(5) { result in
    comboHandler(result)
}

我在这里问的具体问题的答案,如果它对其他人有用,结果证明是采用与 Memoization 类似的方法。严格来说是缓存,而不是memoization,但是将step函数包装在另一个函数中,接受一个缓存key参数的概念是一样的。