我正在尝试编写自己的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))正如我所料,第一次测试通过,但第二次测试失败并显示以下消息:
抛出异常:nunit.framework.dll中的'NUnit.Framework.AssertionException'使用嵌套工作流测试/返回成功:失败:预期: <Success 5>但是: <Failure>
我不明白。 x没有产生,为什么它影响我的父工作流程? 如果我动了! 以下产生测试通过。 我正在盯着我的Combine实现,它在我看来,对于Failure*Success对,参数的实际顺序不会影响结果,但它似乎确实如此
I'm trying to write my own Either builder as part of my quest to learn computation expressions in f#, but I have hit a wall with what I think is issue with Combine method. My code so far:
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()With this code I test the Combine with two tests:
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))The first test passes, as I would expect, but the second test fails with following message:
Exception thrown: 'NUnit.Framework.AssertionException' in nunit.framework.dll either tests/returns success with nested workflows: Failed: Expected: <Success 5> But was: <Failure>
I don't get it. The x is not yielded, so why does it influence my parent workflow? If I move let! below yield the test passes. I'm staring at my Combine implementation and it looks for me that for Failure*Success pair the actual order of arguments would not influence the result, but yet it seems like it does
最满意答案
do! let! 表达式中的子句去掉了Bind调用。 这意味着当你let! x = ...时你的Bind被调用let! x = ... 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! x = 。
为了使内部计算“永远不会成为外部计算的一部分”,只需使用let (没有爆炸):
let value = either { let x = either { yield! Failure } yield 5 }这将正确返回Success 5 。
do! and let! clauses within the expression get desugared to Bind calls. This means that your Bind is called when you do let! x = ....
More specifically, your second example gets desugared into the following:
let value = either.Bind( either.YieldFrom Failure, // yield! Failure fun x -> // let! x = either.Yield 5 // yield 5 )So it never even gets to yield 5 - the computation stops at let! x =.
In order for the inner computation to "never become part" of the outer one, just use let (without the bang):
let value = either { let x = either { yield! Failure } yield 5 }This will correctly return Success 5.
更多推荐
发布评论