pad*_*pad 24 dsl f# query-expressions computation-expression
扩展计算表达式的意思是使用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
}
Run Code Online (Sandbox Code Playgroud)
我想知道如何在不使用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
Run Code Online (Sandbox Code Playgroud)
我有几个密切相关的问题:
For成员的情况下定义或使用扩展计算表达式(即提供一个小的完整示例)?如果他们不再是monad我不担心,我对他们开发DSL感兴趣.let!和return!?如果是,是否有任何理由不这样做?我问这些问题是因为我没有遇到任何使用let!和的例子return!.kvb*_*kvb 14
我很高兴你喜欢IL的例子.了解表达式如何被去除的最佳方法可能是查看规范(虽然它有点密集......).
在那里我们可以看到类似的东西
C {
op1
op2
}
Run Code Online (Sandbox Code Playgroud)
如下去了:
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()))
Run Code Online (Sandbox Code Playgroud)
至于为什么Yield()使用而不是Zero,这是因为如果范围中有变量(例如因为你使用了一些变量lets,或者在for循环中等等),那么你会得到Yield (v1,v2,...)但Zero显然不能以这种方式使用.请注意,这意味着let x = 1在Tomas的lr示例中添加多余的将无法编译,因为Yield将使用类型的参数调用int而不是unit.
还有另一个技巧可以帮助理解计算表达式的编译形式,即(ab)使用F#3中计算表达式的自动引用支持.只需定义一个do-nothing Quote成员并使其Run返回其参数:
member __.Quote() = ()
member __.Run(q) = q
Run Code Online (Sandbox Code Playgroud)
现在你的计算表达式将评估它的desugared形式的引用.这在调试时非常方便.
我不得不承认,当你使用像属性这样的查询表达式功能时,我并不完全理解计算表达式是如何工作的CustomOperation.但是,我的一些实验可能会有所帮助......
首先,我认为不可能将标准计算表达式特征(return!等)与自定义操作自由组合.显然允许一些组合,但不是全部.例如,如果我定义自定义操作left,return!那么我之前 只能使用自定义操作return!:
// Does not compile // Compiles and works
moves { return! lr moves { left
left } return! lr }
Run Code Online (Sandbox Code Playgroud)
对于只使用自定义操作,最常见的cusotom操作的计算(orderBy,reverse而这种)有一个类型M<'T> -> M<'T>,其中M<'T>是代表我们正在构建的东西(如表)的一些(可能是通用)类型.
例如,如果我们想要构建一个表示左/右移动序列的值,我们可以使用以下Commands类型:
type Command = Left | Right
type Commands = Commands of Command list
Run Code Online (Sandbox Code Playgroud)
自定义操作,如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])
Run Code Online (Sandbox Code Playgroud)
请注意,这是从不同的yield返回只是一个单一的操作-或命令-因此yield需要Combine,如果您使用自定义操作多个单独的步骤结合起来,那么你永远不需要任何合并,因为自定义操作逐步构建的Commands价值作为一个整体.它只需要在开始时使用的一些初始空 Commands值...
现在,我希望看到Zero那里,但它实际上调用Yield单位作为参数,所以你需要:
member x.Yield( () ) =
Commands[]
Run Code Online (Sandbox Code Playgroud)
我不确定为什么会这样,但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)
Run Code Online (Sandbox Code Playgroud)
(在某些时候,翻译将开始要求For和Return,但在这里,他们可以被定义喜欢Bind和Yield-我不完全理解的时候是用来替代).
然后你可以这样写:
let moves = MovesBuilder()
let lr =
moves { left
right }
let res =
moves { left
do! lr
left
do! lr }
Run Code Online (Sandbox Code Playgroud)