fsprojects / FSharpPlus

Extensions for F#
https://fsprojects.github.io/FSharpPlus
Apache License 2.0
834 stars 95 forks source link

Revisit Computation Expressions design #140

Open gusty opened 5 years ago

gusty commented 5 years ago

I'm opening this issue as a centralized place to discuss and review the current design of the generic Computation Expressions.

The main issue of the current design was highlighted by @cmeeren recently and has to do with the "autosense" design decision, to allow to figure out automatically in many trivial cases the evaluation strategy of the Computation (lazy vs strict).

We can discuss to change it, make it more explicit, or add another set of builders that are more strict and keep backwards compatibility.

Please raise all your questions, opinions, proposals, here.

wallymathieu commented 5 years ago

I think it would make sense to add some more documentation about MonadPlus. Perhaps something like:

Monad plus is an additive monad. Use it when you expect one or more results. The name comes from Haskell. See computation-zoo for more information.

wallymathieu commented 5 years ago

I've added a pull request where I've tried to fill in some of the information: #141

wallymathieu commented 3 years ago

Any thoughts on this @cmeeren?

cmeeren commented 3 years ago

About what? the last update I can see in this issue is from almost 2 years ago.

nikolamilekic commented 3 years ago

I just wanted to add my two cents to this before closing #346.

I think that computation expressions should not deviate from universally understood concepts as that would surprise and confuse users (especially newbies such as myself). A try...with operation for instance should always handle exceptions and not propagate them (in other words it should behave similarly to the try...with expression outside of a computation expression). This is currently not the case for lazy monads but is the case for strict monads, and I'm hoping this discrepancy will be fixed by any future redesign.

cmeeren commented 3 years ago

This is currently not the case for lazy monads

Wait, what? Are you saying that if I replaced the built-in async with monad from FSharpPlus, the exception handling would be very different? That is indeed confusing (and yet another reason I'm sceptical of using the generic CEs from FSharpPlus).

wallymathieu commented 3 years ago

Perhaps in order for a redesign to happen, we need to have some idea about how what kind of design should be suitable. Since v2 is coming up we have more room for breaking changes.

wallymathieu commented 3 years ago

From what I hear it sounds like there would be value in having more strict builders in order to avoid confusion.

gusty commented 3 years ago

I think the issue is that try .. with has different requirements when the monad is lazy.

For a strict monad, we can use the generic try .. with which is already included and working in the monad.strict (or simply monad') generic computation expression.

Lazy monads on the other hand can't use a generic try .. with and each type needs to provide a specific TryWith implementation.

At the moment we have to educate the user to prevent him to write such code but ideally a workflow that is not lazy over a type that doesn't provide a TryWith method should not compile and throw a compiler error stating that.

I'm not sure if that's achievable, maybe if we move the generic TryWith from the Monad file to the strict builders and replace them with a compiler error message will address the issue, at the cost of removing the possibility of customizing the TryWith for strict monads.

@nikolamilekic even withouth this library, try .. with is used in all common F# lazy monads, namely the built-in seq and async.

gusty commented 3 years ago

Actually, looking at the code, we already call a generic try with for strict builders. So, all we have to do is to fail at compile-time in the generic TryWith

See #347