There is an important difference between the async
computation builder and my imperative
builder.
In async
, you cannot create a useful computation that does not return a value. This means that Async<'T>
represents a computation that will eventually produce a value of type 'T
. In this case, the async.Zero
method has to return unit
and has a signature:
async.Zero : unit -> Async<unit>
For imperiatve
builder, the type Imperative<'T>
represents a computation that may or may not return a value. If you look at the type declaration, it looks as follows:
type Imperative<'T> = unit -> option<'T>
This means that the Zero
operation (which is used when you write if
without else
) can be computation of any type. So, imperative.Zero
method returns a computation of any type:
imperative.Zero : unit -> Imperative<'T>
This is a fundamental difference which also explains why you can create if
without else
branch (because the Zero
method can create computation of any type). This is not possible for async
, because Zero
can only create unit
-returning values.
So the two computations have different structures. In particular, "imperative" computations have monoidal structure and async workflows do not. In more details, you can find the explanation in our F# Computation Zoo paper