3

我正在尝试编写自己的 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()

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

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))

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

抛出异常:nunit.framework.dll 中的“NUnit.Framework.AssertionException”通过嵌套工作流测试/返回成功:失败:预期:<Success 5> 但是: <Failure>

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

4

1 回答 1

4

do!表达式中的andlet!子句被取消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

于 2017-11-13T20:50:00.333 回答