wiki:Design/MonadFail

MonadFail

This page contains some notes and ideas that predate the official MonadFail Proposal (MFP) which can be found at

prime:Libraries/Proposals/MonadFail

The Basic Idea

The basic idea is to split the Monad class into

class Applicative m => Monad m where
  (>>=)  :: m a -> (a -> m b) -> m b

  (>>)   :: m a -> m b -> m b
  m >> k = m >>= \_ -> k 

  return :: a -> m a -- still in Monad for historic reasons
  return = pure

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

and adapt do-desugaring accordingly. See https://github.com/quchen/articles/blob/master/monad_fail.md for more details.

Misc Ideas

To aid transition, we can keep fail in Monad, and start out with

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

(or maybe even require MonadPlus, as fail _ = mzero is a sensible default). This is a comparable situation as with post-AMP pure/return , where the now redundant return (which is now an alias for pure) is to be phased out into an ordinary top-level (non-method) function in the long term.

The MonadFail(mfail) desugaring of do could then be enabled via a language pragma {-# LANGUAGE MonadFail -#}, allowing for have a future -XHaskell201x to switch that feature on by default, while retaining -XHaskell2010 with the current old Monad(fail) desugaring semantics.

Monad(fail) could default to MonadFail(mfail) via -XDefaultSignatures

History

In Haskell 1.4 (postscript) fail was not part of the Monad class. Instead there was a separate MonadZero class containing the zero operation whose purpose was to handle pattern-failures in do-syntax (akin to what fail does today). Here are the original Haskell 1.4 class definitions quoted from section "6.2.5 Monadic Classes":

class Functor f where
  map :: (a -> b) -> (f a -> f b)

class Monad m where
  (>>=)  :: m a -> (a -> m b) -> m b
  (>>)   :: m a -> m b -> m b
  return :: a -> m a

class (Monad m) => MonadZero m where
  zero :: m a

class (MonadZero m) => MonadPlus m where
  (++) :: m a -> m a -> m a

However, when Haskell 98 was drafted issues with irrefutable patterns lead to MonadZero being folded into the Monad class (but it doesn't seem to have been an unanimous nor easy decision back then).

The issue is highlighted by deconstructing a monadic action returning a single-constructor value:

f :: Monad m => m (a,b) -> m a
f m1 = do { x <- m1; return (fst x) }

g :: ??? m => m (a,b) -> m a
g m1 = do { (a,_) <- m1; return a }

h :: Monad m => m (a,b) -> m a
h m1 = do { ~(a,_) <- m1; return a }

Should ??? for g be Monad or MonadZero? The single-constructor pattern match will never fail ("unfailable"), so zero is never used. In fact, in Haskell 1.4 g would only require Monad, but requires the concept of `"unfailable" pattern matches (in addition to "irrefutable" pattern matches).

Related Concepts/Proposals

Last modified 2 years ago Last modified on Nov 25, 2015 12:19:36 PM