I want to write the following code:
let someAsync () = async {
if 1 > 2 then return true // Error "this expression is expected to have type unit ..."
// I want to place much code here
return false
}
F# for some reason thinks that I need to write it like that:
let someAsync () = async {
if 1 > 2 then return true
else
// Much code here (indented!)
return false
}
In latter case no error message is produced. But in my view both pieces of code are equivalent. Is there any chance I could avoid unnecessary nesting and indentation?
UPD. What I am asking is possible indeed! Please take a look at example, see section Real world example
I will quote the code:
let validateName(arg:string) = imperative {
if (arg = null) then return false // <- HERE IT IS
let idx = arg.IndexOf(" ")
if (idx = -1) then return false // <- HERE IT IS
// ......
return true
}
So, it is possible, the only question is if it is possible to implement somehow in async
, via an extension to module or whatever.
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