Loc*_*min 3 f# computation-expression
我希望下面的示例计算表达式和值返回 6。对于某些数字并没有像我期望的那样产生。我缺少获得结果的步骤是什么?谢谢!
type AddBuilder() =
let mutable x = 0
member _.Yield i = x <- x + i
member _.Zero() = 0
member _.Return() = x
let add = AddBuilder()
(* Compiler tells me that each of the numbers in add don't do anything
and suggests putting '|> ignore' in front of each *)
let result = add { 1; 2; 3 }
(* Currently the result is 0 *)
printfn "%i should be 6" result
Run Code Online (Sandbox Code Playgroud)
注意:这只是为了创建我自己的计算表达式来扩展我的学习。Seq.sum将是一个更好的方法。我对这个例子完全忽略了计算表达式的价值并且不利于学习的想法持开放态度。
这里有很多错误。
首先,让我们从单纯的力学开始。
为了Yield调用该方法,花括号内的代码必须使用yield关键字:
let result = add { yield 1; yield 2; yield 3 }
Run Code Online (Sandbox Code Playgroud)
但是现在编译器会抱怨你还需要一个Combine方法。看, 的语义yield是它们中的每一个都产生一个完成的计算,一个结果值。因此,如果您想拥有多个,则需要某种方法将它们“粘合”在一起。这就是该Combine方法的作用。
由于您的计算构建器实际上并不产生任何结果,而是改变其内部变量,计算的最终结果应该是该内部变量的值。所以这就是Combine需要返回的内容:
member _.Combine(a, b) = x
Run Code Online (Sandbox Code Playgroud)
但是现在编译器再次抱怨:你需要一个Delay方法。Delay不是绝对必要的,但它是减少性能缺陷所必需的。当计算由许多“部分”组成时(例如在多个yields的情况下),通常情况下应该丢弃其中一些。在这些情况下,评估所有这些然后丢弃一些是低效的。因此,编译器插入对 的调用Delay:它接收一个函数,当调用该函数时,将评估计算的“部分”,并Delay有机会将此函数放入某种延迟容器中,以便以后Combine可以决定哪个那些要丢弃的容器和要评估的容器。
但是,在您的情况下,由于计算结果无关紧要(请记住:您没有返回任何结果,您只是在改变内部变量),因此Delay可以执行它接收到的函数以使其产生侧面效果(即 - 改变变量):
member _.Delay(f) = f ()
Run Code Online (Sandbox Code Playgroud)
现在计算终于编译通过了,你看:它的结果是6。这个结果来自Combine返回的任何东西。尝试像这样修改它:
member _.Combine(a, b) = "foo"
Run Code Online (Sandbox Code Playgroud)
现在你的计算结果突然变成了"foo"。
现在,让我们继续讨论语义。
上述修改将使您的程序编译甚至产生预期的结果。但是,我认为您首先误解了计算表达式的整个想法。
构建器不应该有任何内部状态。相反,它的方法应该操作某种复杂的值,一些方法创建新值,一些修改现有值。例如,seq构建器1操作序列。这就是它处理的值的类型。不同的方法创建新的序列(Yield)或以某种方式转换它们(例如Combine),最终的结果也是一个序列。
在您的情况下,您的构建器需要操作的值看起来像是数字。而最终的结果也将是一个数字。
所以让我们看看方法的语义。
该Yield方法应该创建您正在操纵的那些值之一。由于您的值是数字,因此Yield应该返回:
member _.Yield x = x
Run Code Online (Sandbox Code Playgroud)
Combine如上所述,该方法应该组合由表达式的不同部分创建的两个这样的值。在您的情况下,由于您希望最终结果为总和,因此Combine应该这样做:
member _.Combine(a, b) = a + b
Run Code Online (Sandbox Code Playgroud)
最后,该Delay方法应该只执行提供的函数。在您的情况下,由于您的值是数字,因此丢弃其中任何一个都没有意义:
member _.Delay(f) = f()
Run Code Online (Sandbox Code Playgroud)
就是这样!使用这三种方法,您可以添加数字:
type AddBuilder() =
member _.Yield x = x
member _.Combine(a, b) = a + b
member _.Delay(f) = f ()
let add = AddBuilder()
let result = add { yield 1; yield 2; yield 3 }
Run Code Online (Sandbox Code Playgroud)
我认为数字不是学习计算表达式的一个很好的例子,因为数字缺乏计算表达式应该处理的内部结构。尝试创建一个maybe构建器来操作Option<'a>值。
额外的好处 - 您已经可以在网上找到并用作参考的实现。
1 seq实际上不是计算表达式。它早于计算表达式,并由编译器以特殊方式处理。但是对于示例和比较来说已经足够了。