for {
resa <- asyncCall(1)
resb <- asyncCall(2)
resc <- asyncCall(3)
} yield new Obj(resa, resb, resc)
which just gets rewritten to asyncCall(1).flatMap(resa => asyncCall(2).flaMap(resb =>
asyncCall(3).map(resc => new Obj(resa, resb, resc))))
This doesn't just work for futures, it works for any monad. This is super amazingly useful. Also, query syntax for LINQ in C#/F# can support this too.I recognise your concern: asynchronicity is infectious if it's manifested as monads. Which sucks. Client code needs to know whether the code it is calling is blocking or asynchronous.
Maybe effect systems can trace through code whether it is 'infected' with blockingness. Without any change showing in the intermediary source code. The blockingness/non-blockingness only manifesting itself at the top of the call graph.
Contrast that with JavaScript where you're forced to use promises/async await. If you don't, you block the whole event loop.
See also "What Color is Your Function": http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y...
In Go I can make an apparently synchronous database query without blocking anything except the calling code.