MonadFail proposal (MFP): Moving fail out of Monad

Edward Kmett ekmett at gmail.com
Tue Jun 9 22:19:00 UTC 2015


+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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/libraries/attachments/20150610/992c58c0/attachment-0001.html>


More information about the Libraries mailing list