I have a computation expression builder that builds up a value as you go, and has many custom operations. However, it does not allow for standard F# language constructs, and I\'
The best place to look is the spec. For example,
b {
let x = e
op x
}
gets translated to
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)
So this shows where things have gone wrong, though it doesn't present an obvious solution. Clearly, Yield
needs to be generalized since it needs to take arbitrary tuples (based on how many variables are in scope). Perhaps more subtly, it also shows that x
is not in scope in the call to add
(see that unbound x
as the second argument to b.Op
?). To allow your custom operators to use bound variables, their arguments need to have the [
attribute (and take functions from arbitrary variables as arguments), and you'll also need to set MaintainsVariableSpace
to true
if you want bound variables to be available to later operators. This will change the final translation to:
b.Op(let x = e in b.Yield(x), fun x -> x)
Building up from this, it seems that there's no way to avoid passing the set of bound values along to and from each operation (though I'd love to be proven wrong) - this will require you to add a Run
method to strip those values back off at the end. Putting it all together, you'll get a builder which looks like this:
type ListBuilder() =
member x.Yield(vars) = Items [],vars
[]
member x.Add((Items current,vars), []f) =
Items (current @ [f vars]),vars
[]
member x.AddMany((Items current, vars), []f) =
Items (current @ f vars),vars
member x.Run(l,_) = l