我试图编写自己的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
对,参数的实际顺序不会影响结果,但它似乎确实如此
答案 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
。