
Hey everyone, I feel like there are a couple of elephants in the room that are sort of important but nobody really addresses them directly. One of them was what became the AMP, and `fail` is another one. I resurrected and refurbished this proposal from a 6 month old Gist because there seemed to be some interest/talk going on in the form of side remarks in other discussions here. The sun shines, let's make some hay! The proposal text can be found at the end of this text (for the mailman archives), or in pretty HTML here: https://github.com/quchen/articles/blob/master/monad_fail.md Although this will technically a language-level change, the impact will mainly be a library and fail-safety detail. For this reason I think this mailing list is more suitable than other more specialized lists. Greetings, David/quchen PS: I would have chosen Fail Removal Proposal so we have a new buzzword, but unfortunately it does not abbreviate well. Call it the "fail of Haskell issue"? ;-) Removing `fail` from `Monad` ============================ The problem ----------- Currently, the `<-` symbol is desugared as follows: ```haskell do pat <- computation >>> let f pat = more more >>> f _ = fail "..." >>> in computation >>= f ``` The problem with this is that `fail` cannot be sensibly implemented for many monads, for example `State`, `IO`, `Reader`. In those cases it defaults to `error`, i.e. the monad has a built-in crash. `MonadFail` class ----------------- To fix this, introduce a new typeclass: ```haskell class Monad m => MonadFail m where fail :: String -> m a ``` Desugaring is then changed to the following: ```haskell -- Explicitly irrefutable pattern: do not add MonadFail constraint do ~pat <- computation >>> let f pat = more more >>> in computation >>= f -- Only one data constructor: do not add MonadFail constraint do (Only x) <- computation >>> let f (Only x) = more more >>> in computation >>= f -- Otherwise: add MonadFail constraint do pat <- computation >>> let f pat = more more >>> f _ = fail "..." >>> in computation >>= f ``` Discussion ---------- - Although for many `MonadPlus` `fail _ = mzero`, a separate `MonadFail` class should be created. A parser might fail with an error message involving positional information, and for STM failure is undefined although it is `MonadPlus`. - The case of one data constructor should emit a warning if the data type is defined via `data`: adding another data constructor can make patterns in unrelated modules refutable. - Some monads use the pattern matching to force evaluation of the binding, for example lazy/strict `StateT`. I'm not sure what exactly the consequences of the above are here; I suspect a strictness annotation or `(>>=)` instead of `do` notation might be sufficient. - Getting the change to work should be boring but painless: all Monad instance declarations involving `fail` will break because the function is removed, and many monadic computations have to be annotated using `~` because they were written under the assumption that `fail` is never called. In both these cases compilation errors/warnings carry sufficient information to fix the source code easily. - Backwards compatibility with many old modules will be broken; I don't see a way around this. Other things to consider ------------------------ - Rename `fail`? It's quite a generic name that would be nice to have in APIs. `failM`? `mfail`? - Remove the `String` argument? (May hurt error reporting on pattern mismatch ..?) - How sensitive would existing code be to subtle changes in the strictness behaviour of `do` notation pattern matching? Applying the change ------------------- Like the AMP, 1. Implement ad-hoc warnings that code will receive a `MonadFail` constraint in a future version. "Either make the patterns irrefutable, or keep in mind that the next compiler version will require a `MonadFail` instance". Since `MonadFail` does not clash with an existing name, it could be introduced to `Control.Monad` right away. 2. Do the switch.

On Mon, Dec 16, 2013 at 11:26:39PM +0100, David Luposchainsky wrote:
I feel like there are a couple of elephants in the room that are sort of important but nobody really addresses them directly. One of them was what became the AMP, and `fail` is another one.
Is it written up somewhere why pattern match failure in 'do' is a 'fail' but pattern match failure elsewhere is just pattern match failure? Malcolm Wallace mentioned that it convenient when writing parsers, and his example is indeed neat, but is has someone done a more substantial investigation the benefits of this special case? Tom

On Mon, Dec 16, 2013 at 2:54 PM, Tom Ellis < tom-lists-haskell-cafe-2013@jaguarpaw.co.uk> wrote:
On Mon, Dec 16, 2013 at 11:26:39PM +0100, David Luposchainsky wrote:
I feel like there are a couple of elephants in the room that are sort of important but nobody really addresses them directly. One of them was what became the AMP, and `fail` is another one.
Is it written up somewhere why pattern match failure in 'do' is a 'fail' but pattern match failure elsewhere is just pattern match failure?
Malcolm Wallace mentioned that it convenient when writing parsers, and his example is indeed neat, but is has someone done a more substantial investigation the benefits of this special case?
AFAIK, it was done this way for list comprehensions. Currently, you can write e.g.
rights = [x | Right x <- listOfEithers]
and it works properly. The list comprehension is desugared to
do { Right x <- listOfEithers; return x }
which becomes
do { listOfEithers >>= \l -> case l of { Right x -> return x; _ -> fail "location" }}
since the Monad instance for lists defines fail = const [], everything works out. The nearly-equivalent code,
listOfEithers >>= \(Right x) -> return x
indeed results in a PatternMatchFail exception. The difference is that in the former case, the fail method is called in lieu of the remainder of the do-expression, whereas the lambda pattern match failure calls throw. Throwing an exception wouldn't work for this because they can't be caught outside IO. (I think this applies to generalized monad comprehensions as well, although I've never tried that)

On Mon, Dec 16, 2013 at 04:47:42PM -0800, John Lato wrote:
On Mon, Dec 16, 2013 at 2:54 PM, Tom Ellis < tom-lists-haskell-cafe-2013@jaguarpaw.co.uk> wrote:
On Mon, Dec 16, 2013 at 11:26:39PM +0100, David Luposchainsky wrote:
I feel like there are a couple of elephants in the room that are sort of important but nobody really addresses them directly. One of them was what became the AMP, and `fail` is another one.
Is it written up somewhere why pattern match failure in 'do' is a 'fail' but pattern match failure elsewhere is just pattern match failure?
Malcolm Wallace mentioned that it convenient when writing parsers, and his example is indeed neat, but is has someone done a more substantial investigation the benefits of this special case?
AFAIK, it was done this way for list comprehensions. Currently, you can write e.g.
rights = [x | Right x <- listOfEithers]
and it works properly. [...]
Sure, but that then just raises the question "why was this special case added for list comprehensions"? To put it another way, if the original implementation of comprehensions and do had treated pattern match failure just as pattern match failure, would a proposal to switch to a 'fail' semantics stand a chance of being accepted? I'm not so sure. Admittedly it allows for some neat code, but it really is a very special case. Tom

On 17.12.2013 14:09, Tom Ellis wrote:
AFAIK, it was done this way for list comprehensions. Currently, you can write e.g.
rights = [x | Right x <- listOfEithers]
and it works properly.
Sure, but that then just raises the question "why was this special case added for list comprehensions"?
List comprehensions are special. - They have their own semantics in the report. Desugaring them to `do` notation is a possible implementation, but I think GHC doesn't do that for performance reasons (-XMonadComprehensions changes this). - List comprehensions can always rely on the empty list to exist, there is no analogon to this in an arbitrary monad. Translations to `do` notation should leave semantics invariant. David

On Tue, Dec 17, 2013 at 8:18 AM, David Luposchainsky
List comprehensions are special.
- They have their own semantics in the report. Desugaring them to `do` notation is a possible implementation, but I think GHC doesn't do that for performance reasons (-XMonadComprehensions changes this).
List comprehensions weren't special (i.e. specific to lists) in Haskell 1.4. They worked like monad comprehensions. Then they were changed to only work with lists in Haskell98 (and that is still the way the standard is), which is why you need to enable an extension to get monad comprehensions now (that it has been re-implemented). And comprehensions, of course, have failure as a built-in notion, due to guards. This was handled by MonadZero, as was pattern match failure. There was also a definition of unfailable patterns. A pattern was unfailable if it could not be refuted except possibly by partially defined values, as I recall. This makes irrefutable patterns trivially unfailable, if you wish to force the issue. And GHC pretty clearly still implements things this way, as seen in adam vogt's mail. -- Dan

On Mon, Dec 16, 2013 at 5:26 PM, David Luposchainsky < dluposchainsky@googlemail.com> wrote:
I feel like there are a couple of elephants in the room that are sort of important but nobody really addresses them directly. One of them was what became the AMP, and `fail` is another one.
AMP is done, except insofar as ghc 7.8 has not been released yet. -- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net

On Mon, Dec 16, 2013 at 5:26 PM, David Luposchainsky
Desugaring is then changed to the following:
```haskell -- Explicitly irrefutable pattern: do not add MonadFail constraint do ~pat <- computation >>> let f pat = more more >>> in computation >>= f
-- Only one data constructor: do not add MonadFail constraint do (Only x) <- computation >>> let f (Only x) = more more >>> in computation >>= f
-- Otherwise: add MonadFail constraint do pat <- computation >>> let f pat = more more >>> f _ = fail "..." >>> in computation >>= f ```
Hello David, GHC can already do this for you. Only `f' below has no MonadFail in the inferred type: {-# LANGUAGE RebindableSyntax #-} import Prelude hiding (fail) class MonadFail m where fail :: String -> m a f x = do x <- x; x g x = do Just y <- return Nothing; x h x = do (a, Just b) <- x; a A specification for "pattern can fail given input that is fully defined" outside of ghc might be http://hackage.haskell.org/package/applicative-quoters-0.1.0.8/docs/src/Cont.... Otherwise I suppose section "3.17.2" of the 2010 report covers this case. Regards, Adam

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 Seems like a fitting time to breathe fresh air into the lungs of the FRP proposal. :-] +1 for the idea (removing MonadFail) & the proposed solution (reintroducing the MonadFail typeclass).
- Rename `fail`? It's quite a generic name that would be nice to have in APIs. `failM`? `mfail`? +1 for failM.
Alexander alexander@plaimi.net https://secure.plaimi.net/~alexander -----BEGIN PGP SIGNATURE----- Version: GnuPG v2 Comment: Using GnuPG with Thunderbird - http://www.enigmail.net/ iF4EAREIAAYFAlQucEIACgkQRtClrXBQc7X+MQD/VDCV/5rFflWVCnrtxbK/JIuz yvRMEeu6S4R7j3aQyiUA/2MJ8m9ZFXmsxgwX/rAj4MUDOEiQwSNm4Fj2R+zG218t =WUUY -----END PGP SIGNATURE-----

On 2013-12-16 at 23:26:39 +0100, David Luposchainsky wrote: [...]
The proposal text can be found at the end of this text (for the mailman archives), or in pretty HTML here: https://github.com/quchen/articles/blob/master/monad_fail.md
For the record (as I missed this thread back in 2013), I'd be enthusiastically +1 on this Fail-Removal-Proposal :-)
participants (9)
-
adam vogt
-
Alexander Berntsen
-
Brandon Allbery
-
Dan Doel
-
David Luposchainsky
-
Herbert Valerio Riedel
-
John Lato
-
Nils Anders Danielsson
-
Tom Ellis