最好看的地方是spec http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/spec.html#_Toc335818835。例如,
b {
let x = e
op x
}
被翻译成
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)
因此,这表明了问题所在,尽管它没有提供明显的解决方案。清楚地,Yield
需要泛化,因为它需要采用任意元组(基于范围内的变量数量)。也许更微妙的是,它还表明x
不在调用范围内add
(请参阅未绑定的x
作为第二个参数b.Op
?)。为了允许您的自定义运算符使用绑定变量,它们的参数需要具有[<ProjectionParameter>]
属性(并从任意变量中获取函数作为参数),并且您还需要设置MaintainsVariableSpace
to true
如果您希望绑定变量可供以后的操作员使用。这会将最终翻译更改为:
b.Op(let x = e in b.Yield(x), fun x -> x)
由此看来,似乎没有办法避免在每个操作之间传递一组绑定值(尽管我很想被证明是错误的)——这将需要您添加一个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