MonadFail proposal (MFP): Moving fail out of Monad

Malcolm Wallace malcolm.wallace at me.com
Wed Jun 10 07:27:54 UTC 2015


I'm a +1 on this proposal as well. In our private Haskell compiler at work, we have had separate Monad and MonadFail classes since 2010, and it is clearly the more principled way to handle partiality: make it visible in the inferred types.  I found that there were very few instances when porting Hackage libraries to our compiler that we came across a need to change type signatures because of MonadFail, and making the change was in all cases easy anyway.

Regards,
    Malcolm

On 9 Jun 2015, at 23:19, Edward Kmett wrote:

> +1 from me for both the spirit and the substance of this proposal. We've been talking about this in the abstract for a while now (since ICFP 2013 or so) and as concrete plans go, this strikes me as straightforward and implementable.
> 
> -Edward
> 
> On Tue, Jun 9, 2015 at 10:43 PM, David Luposchainsky <dluposchainsky at googlemail.com> wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
> 
> Hello *,
> 
> the subject says it all. After we successfully put `=>`
> into Monad, it is time to remove something in return: `fail`.
> 
> Like with the AMP, I wrote up the proposal in Markdown
> format on Github, which you can find below as a URL, and in
> verbatim copy at the end of this email. It provides an
> overview over the intended outcome, which design decisions
> we had to take, and how our initial plan for the transition
> looks like. There are also some issues left open to
> discussion.
> 
> https://github.com/quchen/articles/blob/master/monad_fail.md
> 
> Here's a short abstract:
> 
> - - Move `fail` from `Monad` into a new class `MonadFail`.
> - - Code using failable patterns will receive a more
>   restrictive `MonadFail` constraint. Code without this
>   constraint will be safe to use for all Monads.
> - - Transition will take at least two GHC releases.
>   GHC 7.12 will include the new class, and generate
>   warnings asking users to make their failable patterns
>   compliant.
> - - Stackage showed an upper bound of less than 500 breaking
>   code fragments when compiled with the new desugaring.
> 
> For more details, refer to the link or the paste at the end.
> 
> 
> Let's get going!
> 
> David aka quchen
> 
> 
> 
> 
> 
> ===============================================================
> ===============================================================
> ===============================================================
> 
> 
> 
> 
> 
> `MonadFail` proposal (MFP)
> ==========================
> 
> A couple of years ago, we proposed to make `Applicative` a superclass of
> `Monad`, which successfully killed the single most ugly thing in Haskell
> as of GHC 7.10.
> 
> Now, it's time to tackle the other major issue with `Monad`: `fail` being a
> part of it.
> 
> You can contact me as usual via IRC/Freenode as *quchen*, or by email to
> *dluposchainsky at the email service of Google*. This file will also be posted
> on the ghc-devs@ and libraries@ mailing lists, as well as on Reddit.
> 
> 
> 
> Overview
> - --------
> 
> - - **The problem** - reason for the proposal
> - - **MonadFail class** - the solution
> - - **Discussion** - explaining our design choices
> - - **Adapting old code** - how to prepare current code to transition smoothly
> - - **Esimating the breakage** - how much stuff we will break (spoiler: not much)
> - - **Transitional strategy** - how to break as little as possible while
> transitioning
> - - **Current status**
> 
> 
> 
> 
> The problem
> - -----------
> 
> Currently, the `<-` symbol is unconditionally 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`. As a consequence, in current Haskell, you can not use
> `Monad`-polymorphic code safely, because although it claims to work for all
> `Monad`s, it might just crash on you. This kind of implicit non-totality baked
> into the class is *terrible*.
> 
> The goal of this proposal is adding the `fail` only when necessary and
> reflecting that in the type signature of the `do` block, so that it can be used
> safely, and more importantly, is guaranteed not to be used if the type
> signature does not say so.
> 
> 
> 
> `MonadFail` class
> - -----------------
> 
> To fix this, introduce a new typeclass:
> 
> ```haskell
> class Monad m => MonadFail m where
>     fail :: String -> m a
> ```
> 
> Desugaring can now be changed to produce this constraint when necessary. For
> this, we have to decide when a pattern match can not fail; if this is the case,
> we can omit inserting the `fail` call.
> 
> The most trivial examples of unfailable patterns are of course those that match
> anywhere unconditionally,
> 
> ```haskell
> do x <- action     >>>     let f x = more
>    more            >>>     in  action >>= f
> ```
> 
> In particular, the programmer can assert any pattern be unfailable by making it
> irrefutable using a prefix tilde:
> 
> ```haskell
> do ~pat <- action     >>>     let f ~pat = more
>    more               >>>     in  action >>= f
> ```
> 
> A class of patterns that are conditionally failable are `newtype`s, and single
> constructor `data` types, which are unfailable by themselves, but may fail
> if matching on their fields is done with failable paterns.
> 
> ```haskell
> data Newtype a = Newtype a
> 
> - -- "x" cannot fail
> do Newtype x <- action            >>>     let f (Newtype x) = more
>    more                           >>>     in  action >>= f
> 
> - -- "Just x" can fail
> do Newtype (Just x) <- action     >>>     let f (Newtype (Just x)) = more
>    more                           >>>         f _ = fail "..."
>                                   >>>     in  action >>= f
> ```
> 
> `ViewPatterns` are as failable as the pattern the view is matched against.
> Patterns like `(Just -> Just x)` should generate a `MonadFail` constraint even
> when it's "obvious" from the view's implementation that the pattern will always
> match. From an implementor's perspective, this means that only types (and their
> constructors) have to be looked at, not arbitrary values (like functions),
> which is impossible to do statically in general.
> 
> ```haskell
> do (view ->  pat) <- action     >>>     let f (view ->  pat) = more
>    more                         >>>         f _ = fail "..."
>                                 >>>     in  action >>= f
> 
> do (view -> ~pat) <- action     >>>     let f (view -> ~pat) = more
>    more                         >>>     in  action >>= f
> ```
> 
> A similar issue arises for `PatternSynonyms`, which we cannot inspect during
> compilation sufficiently. A pattern synonym will therefore always be considered
> failable.
> 
> ```haskell
> do PatternSynonym x <- action     >>>     let f PatternSynonym x = more
>    more                           >>>     in f _ = fail "..."
>                                   >>>     in  action >>= f
> ```
> 
> 
> 
> Discussion
> - ----------
> 
> - - Although for many `MonadPlus` `fail _ = mzero`, a separate `MonadFail` class
>   should be created instead of just using that.
> 
>     - A parser might fail with an error message involving positional
>       information. Some libraries, like `Binary`, provide `fail` as their
>       only interface to fail a decoding step.
> 
>     - Although `STM` is `MonadPlus`, it uses the default `fail = error`. It
>       will therefore not get a `MonadFail` instance.
> 
> - - What laws should `fail` follow? **Left zero**,
> 
>   ```haskell
>   ∀ s f.  fail s >>= f  ≡  fail s
>   ```
> 
>   A call to `fail` should abort the computation. In this sense, `fail` would
>   become a close relative of `mzero`. It would work well with the common
>   definition `fail _ = mzero`, and give a simple guideline to the intended
>   usage and effect of the `MonadFail` class.
> 
> - - Rename `fail`? **No.** Old code might use `fail` explicitly and we might
>   avoid breaking it, the Report talks about `fail`, and we have a solid
>   migration strategy that does not require a renaming.
> 
> - - Remove the `String` argument? **No.** The `String` might help error reporting
>   and debugging. `String` may be ugly, but it's the de facto standard for
>   simple text in GHC. No high performance string operations are to be
>   expected with `fail`, so this breaking change would in no way be justified.
>   Also note that explicit `fail` calls would break if we removed the argument.
> 
> - - How sensitive would existing code be to subtle changes in the strictness
>   behaviour of `do` notation pattern matching? **It doesn't.** The
>   implementation does not affect strictness at all, only the desugaring step.
>   Care must be taken when fixing warnings by making patterns irrefutable using
>   `~`, as that *does* affect strictness. (Cf. difference between lazy/strict
>   State)
> 
> - - The `Monad` constraint for `MonadFail` seems unnecessary. Should we drop or
>   relax it? What other things should be considered?
> 
>   - Applicative `do` notation is coming sooner or later, `fail` might be useful
>     in this more general scenario. Due to the AMP, it is trivial to change
>     the `MonadFail` superclass to `Applicative` later. (The name will be a bit
>     misleading, but it's a very small price to pay.)
>   - The class might be misused for a strange pointed type if left without
>     any constraint. This is not the intended use at all.
> 
>   I think we should keep the `Monad` superclass for three main reasons:
> 
>   - We don't want to see `(Monad m, MonadFail m) =>` all over the place.
>   - The primary intended use of `fail` is for desugaring do-notation anyway.
>   - Retroactively removing superclasses is easy, but adding them is hard
>     (see AMP).
> 
> 
> 
> 
> Adapting old code
> - -----------------
> 
> - - Help! My code is broken because of a missing `MonadFail` instance!
> 
>   *Here are your options:*
> 
>     1. Write a `MonadFail` instance (and bring it into scope)
> 
>        ```haskell
>        #if !MIN_VERSION_base(4,11,0)
>        -- Control.Monad.Fail import will become redundant in GHC 7.16+
>        import qualified Control.Monad.Fail as Fail
>        #endif
>        import Control.Monad
> 
>        instance Monad Foo where
>          (>>=) = <...bind impl...>
>          -- NB: `return` defaults to `pure`
> 
>        #if !MIN_VERSION_base(4,11,0)
>          -- Monad(fail) will be removed in GHC 7.16+
>          fail = Fail.fail
>        #endif
> 
>        instance MonadFail Foo where
>          fail = <...fail implementation...>
>        ```
> 
>     2. Change your pattern to be irrefutable
> 
>     3. Emulate the old behaviour by desugaring the pattern match by hand:
> 
>        ```haskell
>        do Left e <- foobar
>           stuff
>        ```
> 
>        becomes
> 
>        ```haskell
>        do x <- foobar
>           e <- case foobar of
>               Left e' -> e'
>               Right r -> error "Pattern match failed" -- Boooo
>           stuff
>        ```
> 
>        The point is you'll have to do your dirty laundry yourself now if you
>        have a value that *you* know will always match, and if you don't handle
>        the other patterns you'll get incompleteness warnings, and the compiler
>        won't silently eat those for you.
> 
> - - Help! My code is broken because you removed `fail` from `Monad`, but my class
>   defines it!
> 
>   *Delete that part of the instance definition.*
> 
> 
> 
> Esimating the breakage
> - ----------------------
> 
> Using our initial implementation, I compiled stackage-nightly, and grepped the
> logs for found "invalid use of fail desugaring". Assuming my implementation
> is correct, the number of "missing `MonadFail`" warnings generated is 487.
> Note that I filtered out `[]`, `Maybe` and `ReadPrec`, since those can be given
> a `MonadFail` instance from within GHC, and no breakage is expected from them.
> 
> The build logs can be found [here][stackage-logs]. Search for "failable
> pattern" to find your way to the still pretty raw warnings.
> 
> 
> 
> 
> Transitional strategy
> - ---------------------
> 
> The roadmap is similar to the [AMP][amp], the main difference being that since
> `MonadFail` does not exist yet, we have to introduce new functionality and then
> switch to it.
> 
> * **GHC 7.12 / base-4.9**
> 
>     - Add module `Control.Monad.Fail` with new class `MonadFail(fail)` so
>       people can start writing instances for it.
> 
>       `Control.Monad` only re-exports the class `MonadFail`, but not its
>       `fail` method.
> 
>       NB: At this point, `Control.Monad.Fail.fail` clashes with
>       `Prelude.fail` and `Control.Monad.fail`.
> 
>     - *(non-essential)* Add a language extension `-XMonadFail` that
>       changes desugaring to use `MonadFail(fail)` instead of `Monad(fail)`.
> 
>       This has the effect that typechecking will infer a `MonadFail` constraint
>       for `do` blocks with failable patterns, just as it is planned to do when
>       the entire thing is done.
> 
>     - Warn when a `do`-block that contains a failable pattern is
>       desugared, but there is no `MonadFail`-instance in scope: "Please add the
>       instance or change your pattern matching." Add a flag to control whether
>       this warning appears.
> 
>     - Warn when an instance implements the `fail` function (or when `fail`
>       is imported as a method of `Monad`), as it will be removed from the
>       `Monad` class in the future. (See also [GHC #10071][trac-10071])
> 
> 3. GHC 7.14
> 
>     - Switch `-XMonadFail` on by default.
>     - Remove the desugaring warnings.
> 
> 3. GHC 7.16
> 
>     - Remove `-XMonadFail`, leaving its effects on at all times.
>     - Remove `fail` from `Monad`.
>     - Instead, re-export `Control.Monad.Fail.fail` as `Prelude.fail` and
>       `Control.Monad.fail`.
>     - `Control.Monad.Fail` is now a redundant module that can be considered
>       deprecated.
> 
> 
> 
> Current status
> - --------------
> 
> - - [ZuriHac 2015 (29.5. - 31.5.)][zurihac]: Franz Thoma (@fmthoma) and me
>   (David Luposchainsky aka @quchen) started implementing the MFP in GHC.
> 
>     - Desugaring to the new `fail` can be controlled via a new langauge
>       extension, `MonadFailDesugaring`.
>     - If the language extension is turned off, a warning will be emitted for
>       code that would break if it was enabled.
>     - Warnings are emitted for types that *have* a *MonadFail* instance. This
>       still needs to be fixed.
>     - The error message are readable, but should be more so. We're still
>       on this.
> - - 2015-06-09: Estimated breakage by compiling Stackage. Smaller than expected.
> 
> 
> 
> [amp]: https://github.com/quchen/articles/blob/master/applicative_monad.md
> [stackage-logs]: https://www.dropbox.com/s/knz0i979skam4zs/stackage-build.tar.xz?dl=0
> [trac-10071]: https://ghc.haskell.org/trac/ghc/ticket/10071
> [zurihac]: https://wiki.haskell.org/ZuriHac2015
> 
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1
> 
> iQEcBAEBAgAGBQJVd0/yAAoJELrQsaT5WQUshbUH/A3W0itVAk7ao8rtxId5unCJ
> 7StriKVkTyLAkkrbRJngM4MHEKiCsoyIgr8kBIwSHgk194GxeP2NCF4ijuBZoDBt
> +Uci+6BCBinV8+OzfrfTcJb4+8iw1j+eLWJ/Nz/JDMDNCiyzyC0SMsqGa+ssOz7H
> /2mqPkQjQgpHuP5PTRLHKPPIsayCQvTbZR1f14KhuMN2SPDE+WY4rqugu//XuIkN
> u1YssIf5l8mEez/1ljaqGL55cTI0UNg2z0iA0bFl/ajHaeQ6mc5BAevWfSohAMW7
> 7PIt13p9NIaMHnikmI+YJszm2IEaXuv47mGgbyDV//nHq3fwWN+naB+1mPX2eSU=
> =vPAL
> -----END PGP SIGNATURE-----
> _______________________________________________
> Libraries mailing list
> Libraries at haskell.org
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
> 
> _______________________________________________
> Libraries mailing list
> Libraries at haskell.org
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries



More information about the ghc-devs mailing list