扩展的计算表达式,而不for..in..do(Extended computation expres

2019-08-05 04:39发布

我通过扩展的计算式的意思是计算表达式通过定义自定义关键字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!

Answer 1:

我很高兴你喜欢的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

现在,您的计算表达式会为其脱糖形式的报价。 调试的事情时,这是非常方便的。



Answer 2:

我得承认我不完全理解,当您使用查询表达式的功能,如计算表达式的工作CustomOperation属性。 但这里有一些我的实验,可以帮助一些言论....

首先,我想是不是可以自由结合的标准计算表达式的功能( return!自定义操作等)。 一些组合显然是允许的,但不是全部。 例如,如果我定义自定义操作leftreturn! 那么我只能用自定义操作return!

// Does not compile              // Compiles and works
moves { return! lr               moves { left 
        left }                           return! lr }

作为仅使用自定义操作,最常见的cusotom操作的计算( orderByreverse和这种)有一个类型M<'T> -> M<'T>其中M<'T>是一些(可能是通用的)型代表我们正在构建的东西(如表)。

例如,如果我们想建立一个代表的左/右移动序列中的值,我们可以使用下面的Commands类型:

type Command = Left | Right 
type Commands = Commands of Command list

像自定义操作leftright ,然后可以将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)

(在某些时候,翻译将开始要求ForReturn ,但在这里,他们可以像定义BindYield -我不完全理解的时候是用来替代)。

然后,你可以写的东西,如:

let moves = MovesBuilder()

let lr = 
  moves { left
          right }    
let res =
  moves { left
          do! lr
          left 
          do! lr }


文章来源: Extended computation expressions without for..in..do