嵌套工作流中的yield问题

时间:2017-11-13 20:23:41

标签: f# either computation-expression

我试图编写自己的Either构建器作为我在f#中学习计算表达式的一部分,但我已经碰到了我认为与Combine方法有关的问题。到目前为止我的代码:

type Result<'a> = 
    | Failure
    | Success of 'a

type EitherBuilder() = 
    member this.Bind(m,f) = 
        match m with
        | Failure -> Failure
        | Success(x) -> f x
    member this.Yield x =
        Success(x)
    member this.YieldFrom x = 
        x
    member this.Combine(a,b) = 
        match a with 
        | Success(_) -> a
        | Failure -> b()
    member this.Delay y = 
        fun () -> y()
    member this.Run(func) = 
        func()

使用此代码,我使用两个测试来测试Combine:

let either = new EitherBuilder()
...
testCase "returns success from 3 yields" <|
    fun _ -> 
        let value = either {
            yield! Failure 
            yield 4
            yield! Failure
            }
        value |> should equal (Success(4))
testCase "returns success with nested workflows" <|
    fun _ -> 
        let value = either {
            let! x = either { 
                yield! Failure 
                } 
            yield 5
            }
        value |> should equal (Success(5))

正如我所料,第一次测试通过,但第二次测试失败并显示以下消息:

  

抛出异常:&#39; NUnit.Framework.AssertionException&#39;在   nunit.framework.dll使用嵌套工作流测试/返回成功:   失败:预期:<Success 5>但是:<Failure>

我不明白。 x没有产生,为什么它会影响我的父工​​作流程?如果我动了!以下产生测试通过。我正在盯着我的Combine实现,它在我看来,对于Failure*Success对,参数的实际顺序不会影响结果,但它似乎确实如此

1 个答案:

答案 0 :(得分:4)

表达式中的

do!let!子句会被Bind次调用。这意味着当您执行Bind时会调用let! x = ...

更具体地说,你的第二个例子被贬低为:

let value = 
    either.Bind(
       either.YieldFrom Failure,   // yield! Failure
       fun x ->                    // let! x =
           either.Yield 5          // yield 5
    )

所以它永远不会到达yield 5 - 计算在let! x =停止。

为了使内部计算“永远不会成为外部计算的一部分”,只需使用let(没有爆炸):

let value = either {
     let x = either { 
         yield! Failure 
         } 
     yield 5
     }

这将正确返回Success 5