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>]属性(并将任意变量的函数作为参数),并且您还需要设置MaintainsVariableSpace为true是否要将绑定变量提供给以后的运算符.这会将最终翻译更改为:
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)