I have the following computation expression builder:
type ExprBuilder() =
member this.Return(x) =
Some x
let expr = new ExprBuilder()
I understand the purpose of methods Return, Zero and Combine, but I don't understand what is the difference between expressions shown below:
let a = expr{
printfn "Hello"
return 1
} // result is Some 1
let c = expr{
return 1
printfn "Hello"
} // do not compile. Combine method required
I also don't understand why in the first case Zero method in not required for printfn statement?
In the first expression, you perform some computation that results in value 1
, and that's it. You don't need a Zero
in it, because Zero
is only needed for return
-less expressions (that's why it's called "zero" - it's what is there when nothing is there), and your expression does have a return
.
To specifically answer your question, Zero
is not required "for the printfn statement", because not every line within the expression gets transformed. When compiling computation expressions, the compiler breaks them up at "special" points, such as let!
, do!
, return
, etc., leaving all the rest of the code between those points intact. In this case, your printfn
call just becomes part of the code that executes before the return
.
In the second expression, you perform two computations: the first one results in value 1
, and second one results in Zero
(which is implicitly assumed when expression lacks a return
). But the whole computation expression can't have two return values, it must have one. So in order to bring results of the two computations together (one might say, combine them), you need the Combine
method.
Besides Combine
and Zero
, you'd also need to implement Delay
for this to work. Multipart (i.e. "combined") computations are also wrapped in Delay
in order to allow the builder to defer evaluation and possibly drop some parts within the Combine
implementation.
I recommend reading through this introduction by Scott Wlaschin, specifically part 3 about Delay
and Run
.