创建 'add' 计算表达式
Creating an 'add' computation expression
我想要下面的示例计算表达式和值 return 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
注意:这只是为了创建自己的计算表达式以扩展我的学习。 Seq.sum
会是更好的方法。我认为这个例子完全忽略了计算表达式的价值,不利于学习。
这里有很多错误。
首先,让我们从简单的机制开始。
为了调用Yield
方法,大括号内的代码必须使用yield
关键字:
let result = add { yield 1; yield 2; yield 3 }
但是现在编译器会抱怨你还需要一个Combine
方法。看,yield
的语义是它们中的每一个都产生一个完成的计算,一个结果值。因此,如果您想拥有多个,则需要某种方式将它们“粘合”在一起。这就是 Combine
方法的作用。
由于您的计算构建器实际上并没有产生任何结果,而是改变了它的内部变量,因此计算的最终结果应该是该内部变量的值。这就是 Combine
需要 return:
member _.Combine(a, b) = x
但是现在编译器又报错了:你需要一个Delay
方法。 Delay
不是绝对必要的,但为了减少性能缺陷,它是必需的。当计算由许多“部分”组成时(例如多个 yield
s 的情况),通常情况下应该丢弃其中的一些部分。在这些情况下,评估所有这些然后丢弃一些是低效的。因此,编译器插入对 Delay
的调用:它接收一个函数,该函数在被调用时将评估计算的“一部分”,并且 Delay
有机会将该函数置于某种形式延迟容器,以便稍后 Combine
可以决定丢弃哪些容器以及评估哪些容器。
然而,在您的情况下,由于计算结果并不重要(请记住:您没有 returning 任何结果,您只是在改变内部变量),Delay
可以只执行它接收到的函数以产生副作用(即 - 改变变量):
member _.Delay(f) = f ()
现在计算终于编译通过了,看吧:它的结果是6
。这个结果来自 Combine
是 return 的任何东西。尝试像这样修改它:
member _.Combine(a, b) = "foo"
现在你的计算结果突然变成了"foo"
。
现在,让我们继续语义。
以上修改将使您的程序编译通过并产生预期的结果。但是,我认为您首先误解了计算表达式的整个概念。
构建器不应该有任何内部状态。相反,它的方法应该操纵某种复杂的值,一些方法创建新值,一些修改现有值。例如,seq
构建器1 操纵 序列。这是它处理的值类型。不同的方法创建新的序列(Yield
)或以某种方式对其进行变换(例如Combine
),最终的结果也是一个序列。
在您的情况下,您的构建器需要操作的值似乎是数字。最后的结果也是一个数字。
那么让我们看看方法的语义。
Yield
方法应该创建您正在操作的那些值之一。由于您的值是数字,因此 Yield
应该 return:
member _.Yield x = x
如上所述,Combine
方法应该组合由表达式的不同部分创建的两个这样的值。在您的情况下,由于您希望最终结果是总和,因此 Combine
应该这样做:
member _.Combine(a, b) = a + b
最后,Delay
方法应该只执行提供的函数。在您的情况下,由于您的值是数字,因此丢弃其中任何一个都没有意义:
member _.Delay(f) = f()
就是这样!通过这三种方法,你可以添加数字:
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 }
我认为数字不是学习计算表达式的一个很好的例子,因为数字缺乏计算表达式应该处理的内部结构。尝试创建一个 maybe
构建器来操纵 Option<'a>
值。
额外的好处 - 您可以在线找到并使用参考的实现。
1 seq
实际上不是计算表达式。它早于计算表达式,并由编译器以特殊方式处理。但是对于示例和比较来说已经足够好了。
我想要下面的示例计算表达式和值 return 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
注意:这只是为了创建自己的计算表达式以扩展我的学习。 Seq.sum
会是更好的方法。我认为这个例子完全忽略了计算表达式的价值,不利于学习。
这里有很多错误。
首先,让我们从简单的机制开始。
为了调用Yield
方法,大括号内的代码必须使用yield
关键字:
let result = add { yield 1; yield 2; yield 3 }
但是现在编译器会抱怨你还需要一个Combine
方法。看,yield
的语义是它们中的每一个都产生一个完成的计算,一个结果值。因此,如果您想拥有多个,则需要某种方式将它们“粘合”在一起。这就是 Combine
方法的作用。
由于您的计算构建器实际上并没有产生任何结果,而是改变了它的内部变量,因此计算的最终结果应该是该内部变量的值。这就是 Combine
需要 return:
member _.Combine(a, b) = x
但是现在编译器又报错了:你需要一个Delay
方法。 Delay
不是绝对必要的,但为了减少性能缺陷,它是必需的。当计算由许多“部分”组成时(例如多个 yield
s 的情况),通常情况下应该丢弃其中的一些部分。在这些情况下,评估所有这些然后丢弃一些是低效的。因此,编译器插入对 Delay
的调用:它接收一个函数,该函数在被调用时将评估计算的“一部分”,并且 Delay
有机会将该函数置于某种形式延迟容器,以便稍后 Combine
可以决定丢弃哪些容器以及评估哪些容器。
然而,在您的情况下,由于计算结果并不重要(请记住:您没有 returning 任何结果,您只是在改变内部变量),Delay
可以只执行它接收到的函数以产生副作用(即 - 改变变量):
member _.Delay(f) = f ()
现在计算终于编译通过了,看吧:它的结果是6
。这个结果来自 Combine
是 return 的任何东西。尝试像这样修改它:
member _.Combine(a, b) = "foo"
现在你的计算结果突然变成了"foo"
。
现在,让我们继续语义。
以上修改将使您的程序编译通过并产生预期的结果。但是,我认为您首先误解了计算表达式的整个概念。
构建器不应该有任何内部状态。相反,它的方法应该操纵某种复杂的值,一些方法创建新值,一些修改现有值。例如,seq
构建器1 操纵 序列。这是它处理的值类型。不同的方法创建新的序列(Yield
)或以某种方式对其进行变换(例如Combine
),最终的结果也是一个序列。
在您的情况下,您的构建器需要操作的值似乎是数字。最后的结果也是一个数字。
那么让我们看看方法的语义。
Yield
方法应该创建您正在操作的那些值之一。由于您的值是数字,因此 Yield
应该 return:
member _.Yield x = x
如上所述,Combine
方法应该组合由表达式的不同部分创建的两个这样的值。在您的情况下,由于您希望最终结果是总和,因此 Combine
应该这样做:
member _.Combine(a, b) = a + b
最后,Delay
方法应该只执行提供的函数。在您的情况下,由于您的值是数字,因此丢弃其中任何一个都没有意义:
member _.Delay(f) = f()
就是这样!通过这三种方法,你可以添加数字:
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 }
我认为数字不是学习计算表达式的一个很好的例子,因为数字缺乏计算表达式应该处理的内部结构。尝试创建一个 maybe
构建器来操纵 Option<'a>
值。
额外的好处 - 您可以在线找到并使用参考的实现。
1 seq
实际上不是计算表达式。它早于计算表达式,并由编译器以特殊方式处理。但是对于示例和比较来说已经足够好了。