我通过扩展的计算式的意思是计算表达式通过定义自定义关键字CustomOperation属性。
当看到有关扩展计算表达式 ,我遇到的@kvb非常酷IL DSL:
let il = ILBuilder()
// will return 42 when called
// val fortyTwoFn : (unit -> int)
let fortyTwoFn =
il {
ldc_i4 6
ldc_i4_0
ldc_i4 7
add
mul
ret
}
我不知道该怎么操作组成,而无需使用for..in..do
结构。 我的直觉是,它开始x.Zero
成员,但我还没有发现任何引用来验证。
如果上面的例子太技术化,这里是一个滑动的部件没有列出类似DSL for..in..do
:
page {
title "Happy New Year F# community"
item "May F# continue to shine as it did in 2012"
code @"…"
button (…)
} |> SlideShow.show
我有几个密切相关的问题:
- 如何定义或使用扩展的计算式没有
For
成员(即提供一个小的完整的例子)? 我不担心太多,如果他们不单子下去,我是在发展中国家的DSL感兴趣。 - 我们可以使用扩展的计算式与
let!
和return!
? 如果是的话,有什么理由不这样做? 我提出这些问题,因为我没有使用中遇到的任何例子let!
和return!
。
我很高兴你喜欢的IL例子。 要了解表达式如何的脱糖,最好的办法可能是看看规范 (虽然这是一个有点迟钝......)。
在那里,我们可以看到,像
C {
op1
op2
}
被脱如下:
T([<CustomOperator>]op1; [<CustomOperator>]op2, [], fun v -> v, true) ⇒
CL([<CustomOperator>]op1; [<CustomOperator>]op2, [], C.Yield(), false) ⇒
CL([<CustomOperator>]op2, [], 〚 [<CustomOperator>]op1, C.Yield() |][], false) ⇒
CL([<CustomOperator>]op2, [], C.Op1(C.Yield()), false) ⇒
〚 [<CustomOperator>]op2, C.Op1(C.Yield()) 〛[] ⇒
C.Op2(C.Op1(C.Yield()))
至于为何Yield()
使用,而不是Zero
,这是因为如果有范围变量(例如,因为你使用了一些lets
,或者是在一个for循环,等等),那么你会得到Yield (v1,v2,...)
但Zero
显然不能采用这种方式。 注意,这意味着添加多余let x = 1
到托马斯的lr
例如将无法被编译,因为Yield
将与类型的参数被称为int
而不是unit
。
还有一个窍门,可以帮助理解的计算式的编译形式,这是(AB)使用F#3.计算表达式自动报价只支持定义一个什么都不做Quote
员,并Run
刚刚返回它的参数:
member __.Quote() = ()
member __.Run(q) = q
现在,您的计算表达式会为其脱糖形式的报价。 调试的事情时,这是非常方便的。
我得承认我不完全理解,当您使用查询表达式的功能,如计算表达式的工作CustomOperation
属性。 但这里有一些我的实验,可以帮助一些言论....
首先,我想是不是可以自由结合的标准计算表达式的功能( return!
自定义操作等)。 一些组合显然是允许的,但不是全部。 例如,如果我定义自定义操作left
和return!
那么我只能用前自定义操作return!
:
// Does not compile // Compiles and works
moves { return! lr moves { left
left } return! lr }
作为仅使用自定义操作,最常见的cusotom操作的计算( orderBy
, reverse
和这种)有一个类型M<'T> -> M<'T>
其中M<'T>
是一些(可能是通用的)型代表我们正在构建的东西(如表)。
例如,如果我们想建立一个代表的左/右移动序列中的值,我们可以使用下面的Commands
类型:
type Command = Left | Right
type Commands = Commands of Command list
像自定义操作left
和right
,然后可以将Commands
进入Commands
并追加了新的一步,以列表的末尾。 就像是:
type MovesBuilder() =
[<CustomOperation("left")>]
member x.Left(Commands c) = Commands(c @ [Left])
[<CustomOperation("right")>]
member x.Right(Commands c) = Commands(c @ [Right])
请注意,这是从不同的yield
,它返回只是一个单一的操作-或命令-等yield
需求Combine
,如果您使用自定义操作多个单独的步骤结合起来,那么你永远不需要任何合并,因为自定义操作逐步建立Commands
值作为整个。 它只需要一些初始空的 Commands
是在开始时使用的值...
现在,我希望看到Zero
存在,但它实际上是调用Yield
与单位作为参数,所以你需要:
member x.Yield( () ) =
Commands[]
我不知道为什么是这样的情况,但是Zero
还是比较通常被定义为Yield ()
所以也许我们的目标是使用默认定义(但正如我所说,我还指望用Zero
这里... )
我想用的计算式结合自定义操作是有道理的。 虽然我对标准计算表达式应该如何使用强烈的意见,我真的没有对任何计算,直觉良好的自定义操作-我认为社会还需要算出这个:-)。 但是,例如,你可以扩展这样的上述计算:
member x.Bind(Commands c1, f) =
let (Commands c2) = f () in Commands(c1 @ c2)
member x.For(c, f) = x.Bind(c, f)
member x.Return(a) = x.Yield(a)
(在某些时候,翻译将开始要求For
和Return
,但在这里,他们可以像定义Bind
和Yield
-我不完全理解的时候是用来替代)。
然后,你可以写的东西,如:
let moves = MovesBuilder()
let lr =
moves { left
right }
let res =
moves { left
do! lr
left
do! lr }