This is kind of a follow-up of this issue in FsControl.
Despite the fact that now both project are merged, F#+ never had a clear way to approach Computation Expressions, it all started with a basic do-notation from an experimental project trying to emulate some Haskell features.
Then a monadPlus
builder was incorporated in order to be able to encode Monoidal Monads and also a linq
builder, to allow to create generic linq queries, similar to C# linq capabilities but with all extra F# stuff. All these efforts were directed towards having a more F#-ish abstraction instead of a simple Haskell do-notation.
However this initial effort seems to be leading us to a dead-end, now I'm convinced that this should be approached in a different way, instead of providing different builders with different number of operations, provide a set of complete builders, each one serving different kind of monads.
There is an interesting example of a maybe builder in the Computation Expression Zoo paper:
let x = maybe {
let a, b = 5, 0
if b = 0 then return! None
printfn "Calculating..."
return (a / b) }
This will fail with a divide by zero exception with the current design which uses always a delayed computation expression.
After experimenting a bit, revisiting the paper about the Computation Expression Zoo and other libraries, I came up with what I think might be an interesting set of cages for the beasts of this zoo.
On one axis we have the type of builders: Delayed and Non-delayed and on another axis we have Monoidal Monads and Sequencing Monads, so this leave us with 4 cages:
- Delayed - MonadPlus:
seq
falls in this cage.
- Delayed - Sequencial: most other delayed monads falls in this cage, like
async
.
- Strict - MonadPlus:
list
and array
are typical examples.
- Strict - Sequencial:
option
falls normally here but since it's also a MonadPlus it can also be enclosed in the above cage.
So the goal is to provide the client of this library a simple mechanism to:
- Get the better default cage for the animal.
- Allow him to switch to a different cage, typical example would be when treating
option
as either a MonadPlus or a Sequencing Monad.
An additional bonus would be to consider Monad Transformers which may have a different Bind
which does a lift
as described in the paper with the AsyncSeq
example which roughly corresponds to the SeqT<Async<'t>>
Monad Transformer.
I think the zoo is huge but at the same time there is a lot of boiler plate code in each case mentioned on that paper and leaving all the choices to the library designer as suggested there, leads the end-user of the library with a 'magic' builder with not very clear semantics and a lot of resposability to the designer , who should decide over different valid approaches (google for the many ways to implement the maybe builder in F#).
Finally, the end-user has always the choice to create his custom builder in the unlikely case this abstraction doesn't suit his need.
In short I think there is a great value in providing set of complete generic builders as an alternative to create them from scratch and also as an alternative to use a library which provides a custom one.