What I mean by extended computation expressions is computation expressions with custom keywords defined via CustomOperation attribute.
When reading about extend
I'm glad you liked the IL example. The best way to understand how expressions are desugared is probably to look at the spec (though it's a bit dense...).
There we can see that something like
C {
op1
op2
}
gets desugared as follows:
T([]op1; []op2, [], fun v -> v, true) ⇒
CL([]op1; []op2, [], C.Yield(), false) ⇒
CL([]op2, [], 〚 []op1, C.Yield() |][], false) ⇒
CL([]op2, [], C.Op1(C.Yield()), false) ⇒
〚 []op2, C.Op1(C.Yield()) 〛[] ⇒
C.Op2(C.Op1(C.Yield()))
As to why Yield()
is used rather than Zero
, it's because if there were variables in scope (e.g. because you used some lets
, or were in a for loop, etc.), then you would get Yield (v1,v2,...)
but Zero
clearly can't be used this way. Note that this means adding a superfluous let x = 1
into Tomas's lr
example will fail to compile, because Yield
will be called with an argument of type int
rather than unit
.
There's another trick which can help understand the compiled form of computation expressions, which is to (ab)use the auto-quotation support for computation expressions in F# 3. Just define a do-nothing Quote
member and make Run
just return its argument:
member __.Quote() = ()
member __.Run(q) = q
Now your computation expression will evaluate to the quotation of its desugared form. This can be pretty handy when debugging things.