I'm very new to F# and I've been reading F# for Fun and Profit. In the Why use F#? series, there's a post describing async code. I ran across the Async.StartChild function and I don't understand why the return value is what it is.
The example:
let sleepWorkflow = async {
printfn "Starting sleep workflow at %O" DateTime.Now.TimeOfDay
do! Async.Sleep 2000
printfn "Finished sleep workflow at %O" DateTime.Now.TimeOfDay
}
let nestedWorkflow = async {
printfn "Starting parent"
let! childWorkflow = Async.StartChild sleepWorkflow
// give the child a chance and then keep working
do! Async.Sleep 100
printfn "Doing something useful while waiting "
// block on the child
let! result = childWorkflow
// done
printfn "Finished parent"
}
My question is why shouldn't Async.StartChild just return Async<'T> instead of Async<Async<'T>>? You have to use let! twice on it. The documentation even states:
This method should normally be used as the immediate right-hand-side of a let! binding in an F# asynchronous workflow [...] When used in this way, each use of StartChild starts an instance of childComputation and returns a completor object representing a computation to wait for the completion of the operation. When executed, the completor awaits the completion of childComputation.
In some testing, adding some calls to sleep, it seems that without the initial let! the child computation is never started.
Why have this return type / behavior? I'm used to C# where calling an async method will always "kick off" the task right away even if you don't await it. In fact, in C#, if the async method doesn't call any async code it will run synchronously.
Edit for clarification:
What is the advantage of this:
let! waiter = Async.StartChild otherComp // Start computation
// ...
let! result = waiter // Block
Compared to if Async.StartChild returned an Async<'T>:
let waiter = Async.StartChild otherComp // Start computation
// ...
let !result = waiter // Block