如何编写一个累积值的计算表达式构建器,并允许使用标准语言结构?

Joe*_*ler 12 f# computation-expression

我有一个计算表达式构建器,可以随时构建一个值,并具有许多自定义操作.但是,它不允许使用标准的F#语言结构,而且我在确定如何添加此支持时遇到了很多麻烦.

为了给出一个独立的例子,这里有一个简单且毫无意义的计算表达式来构建F#列表:

type Items<'a> = Items of 'a list

type ListBuilder() =
    member x.Yield(()) = Items []

    [<CustomOperation("add")>]
    member x.Add(Items current, item:'a) =
        Items [ yield! current; yield item ]

    [<CustomOperation("addMany")>]
    member x.AddMany(Items current, items: seq<'a>) =
        Items [ yield! current; yield! items ]

let listBuilder = ListBuilder()

let build (Items items) = items
Run Code Online (Sandbox Code Playgroud)

我可以使用它来构建列表就好了:

let stuff =
    listBuilder {
        add 1
        add 5
        add 7
        addMany [ 1..10 ]
        add 42
    } 
    |> build
Run Code Online (Sandbox Code Playgroud)

但是,这是编译器错误:

listBuilder {
    let x = 5 * 39
    add x
}

// This expression was expected to have type unit, but
// here has type int.
Run Code Online (Sandbox Code Playgroud)

这是这样的:

listBuilder {
    for x = 1 to 50 do
        add x
}

// This control construct may only be used if the computation expression builder
// defines a For method.
Run Code Online (Sandbox Code Playgroud)

我已经阅读了所有可以找到的文档和示例,但有些东西我没有得到.我尝试的每个.Bind().For()方法签名只会导致越来越混乱的编译器错误.我可以找到的大多数示例要么随你构建一个值,要么允许常规的F#语言结构,但是我找不到同时执行这两个操作的示例.

如果有人可以通过向我展示如何采用这个示例并在构建器中添加对let绑定和for循环的支持(至少 - using,while并且try/catch会很棒,但如果有人让我开始我可能会想出来的话)那么我将能够感激地将课程应用到我的实际问题中.

kvb*_*kvb 11

最好看的是规格.例如,

b {
    let x = e
    op x
}
Run Code Online (Sandbox Code Playgroud)

被翻译成

   T(let x = e in op x, [], fun v -> v, true)
=> T(op x, {x}, fun v -> let x = e in v, true)
=> [| op x, let x = e in b.Yield(x) |]{x}
=> b.Op(let x = e in in b.Yield(x), x)
Run Code Online (Sandbox Code Playgroud)

所以这表明事情出了问题,虽然没有明显的解决方案.显然,Yield需要进行推广,因为它需要采用任意元组(基于范围内有多少变量).也许更巧妙的是,它还表明x调用范围不在add(参见未绑定x的第二个参数b.Op?).要允许自定义运算符使用绑定变量,它们的参数需要具有该[<ProjectionParameter>]属性(并将任意变量的函数作为参数),并且您还需要设置MaintainsVariableSpacetrue是否要将绑定变量提供给以后的运算符.这会将最终翻译更改为:

b.Op(let x = e in b.Yield(x), fun x -> x)
Run Code Online (Sandbox Code Playgroud)

从这一点开始,似乎没有办法避免在每个操作之间传递绑定值的集合(虽然我希望被证明是错误的) - 这将要求你添加一个Run方法来剥离这些值在最后.总而言之,你会得到一个看起来像这样的建设者:

type ListBuilder() =
    member x.Yield(vars) = Items [],vars

    [<CustomOperation("add",MaintainsVariableSpace=true)>]
    member x.Add((Items current,vars), [<ProjectionParameter>]f) =
        Items (current @ [f vars]),vars

    [<CustomOperation("addMany",MaintainsVariableSpace=true)>]
    member x.AddMany((Items current, vars), [<ProjectionParameter>]f) =
        Items (current @ f vars),vars

    member x.Run(l,_) = l
Run Code Online (Sandbox Code Playgroud)