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.
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
-
Although for many
MonadPlus
fail _ = mzero
, a separateMonadFail
class should be created. A parser might fail with an error message involving positional information, and for STM failure is undefined although it isMonadPlus
. -
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 thatfail
is never called. In both these cases compilation errors/warnings carry sufficient information to fix the source code easily.
Like the AMP,
-
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 aMonadFail
instance". SinceMonadFail
does not clash with an existing name, it could be introduced toControl.Monad
right away. -
Do the switch.