Skip to content

Instantly share code, notes, and snippets.

@quchen
Last active December 17, 2015 16:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save quchen/5642483 to your computer and use it in GitHub Desktop.
Save quchen/5642483 to your computer and use it in GitHub Desktop.
Removing 'fail' from the Monad typeclass

Removing fail from Monad

The problem

Currently, the <- symbol is desugared as follows:

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:

class Monad m => MonadFail m where
      fail :: String -> m a

Desugaring is then changed to the following:

-- 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 a new data constructor can make patterns in unrelated modules refutable.

  • 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.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment