Removing MonadFail from Monad

David Luposchainsky dluposchainsky at googlemail.com
Mon Dec 16 22:26:39 UTC 2013


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.


More information about the Libraries mailing list