Removing MonadFail from Monad

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
16 messages Options
Reply | Threaded
Open this post in threaded view
|

Removing MonadFail from Monad

David Luposchainsky
Hey everyone,

I feel like there are a couple of elephants in the room that are sort of
important but nobody really addresses them directly. One of them was
what became the AMP, and `fail` is another one.
I resurrected and refurbished this proposal from a 6 month old Gist
because there seemed to be some interest/talk going on in the form of
side remarks in other discussions here. The sun shines, let's make some hay!

The proposal text can be found at the end of this text (for the mailman
archives), or in pretty HTML here:
https://github.com/quchen/articles/blob/master/monad_fail.md


Although this will technically a language-level change, the impact will
mainly be a library and fail-safety detail. For this reason I think this
mailing list is more suitable than other more specialized lists.


Greetings,
David/quchen


PS: I would have chosen Fail Removal Proposal so we have a new buzzword,
but unfortunately it does not abbreviate well. Call it the "fail of
Haskell issue"? ;-)







Removing `fail` from `Monad`
============================



The problem
-----------


Currently, the `<-` symbol is 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`, i.e. the monad has a built-in crash.



`MonadFail` class
-----------------

To fix this, introduce a new typeclass:

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

Desugaring is then changed to the following:

```haskell
-- Explicitly 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 another data constructor can make
patterns in unrelated modules refutable.

- Some monads use the pattern matching to force evaluation of the
binding, for example lazy/strict `StateT`. I'm not sure what exactly the
consequences of the above are here; I suspect a strictness annotation or
`(>>=)` instead of `do` notation might be sufficient.

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

- Backwards compatibility with many old modules will be broken; I don't
see a way around this.



Other things to consider
------------------------

- Rename `fail`? It's quite a generic name that would be nice to have in
APIs. `failM`? `mfail`?

- Remove the `String` argument? (May hurt error reporting on pattern
mismatch ..?)

- How sensitive would existing code be to subtle changes in the
strictness behaviour of `do` notation pattern matching?



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.
_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

Tom Ellis
On Mon, Dec 16, 2013 at 11:26:39PM +0100, David Luposchainsky wrote:
> I feel like there are a couple of elephants in the room that are sort of
> important but nobody really addresses them directly. One of them was
> what became the AMP, and `fail` is another one.

Is it written up somewhere why pattern match failure in 'do' is a 'fail' but
pattern match failure elsewhere is just pattern match failure?

Malcolm Wallace mentioned that it convenient when writing parsers, and his
example is indeed neat, but is has someone done a more substantial
investigation the benefits of this special case?

Tom

_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

David Luposchainsky
In reply to this post by David Luposchainsky
Hey Andreas,

On 16.12.2013 23:39, Andreas Abel wrote:
> "Only one data constructor" should be understood hereditarily; the
> description is not entirely accurate:
>
> -- Only one data constructor: do not add MonadFail constraint
> do (Only x) <- computation     >>>     let f (Only x) = more
>    more                        >>>     in  computation >>= f
>
> (Only x) should be pat such that every constructor in pat is an
> "only-one" constructor.

Do you mean in case patterns of nested "Only" types appear, like
`Only (Only' x)`? I hadn't thought of that, good point.


>> - The case of one data constructor should emit a warning if the data
>> type is defined via `data`: adding another data constructor can make
>> patterns in unrelated modules refutable.
>
> I don't understand the restriction "is defined via data" since I am not
> aware of defining constructors outside of data or with something other
> than the data keyword.  Please clarify.

I meant "data and not newtype". If "Only" is a newtype data constructor,
the pattern is irrefutable by design, is it not?


Greetings,
David/quchen
_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

Roman Cheplyaka-2
* David Luposchainsky <[hidden email]> [2013-12-16 23:57:19+0100]
> > I don't understand the restriction "is defined via data" since I am not
> > aware of defining constructors outside of data or with something other
> > than the data keyword.  Please clarify.
>
> I meant "data and not newtype". If "Only" is a newtype data constructor,
> the pattern is irrefutable by design, is it not?

One notable case of one-constructor types defined not via data is
tuples. You certainly don't want warnings for that!

Regarding newtypes vs data, I'm not so sure it should make a difference.
It's customary to make one-field types newtypes, and then, if one
realizes that more fields are needed, turn them into proper data types.

Roman

_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries

signature.asc (853 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

Brandon Allbery
In reply to this post by David Luposchainsky
On Mon, Dec 16, 2013 at 5:26 PM, David Luposchainsky <[hidden email]> wrote:
I feel like there are a couple of elephants in the room that are sort of
important but nobody really addresses them directly. One of them was
what became the AMP, and `fail` is another one.

AMP is done, except insofar as ghc 7.8 has not been released yet.

--
brandon s allbery kf8nh                               sine nomine associates
[hidden email]                                  [hidden email]
unix, openafs, kerberos, infrastructure, xmonad        http://sinenomine.net

_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

David Luposchainsky
On 17.12.2013 00:33, Brandon Allbery wrote:
> AMP is done, except insofar as ghc 7.8 has not been released yet.

I heard about that, yes ;-)

(Before I proposed the AMP I had a similar feeling as a few hours ago,
that's why I mentioned it.)

David
_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

John Lato-2
In reply to this post by Tom Ellis



On Mon, Dec 16, 2013 at 2:54 PM, Tom Ellis <[hidden email]> wrote:
On Mon, Dec 16, 2013 at 11:26:39PM +0100, David Luposchainsky wrote:
> I feel like there are a couple of elephants in the room that are sort of
> important but nobody really addresses them directly. One of them was
> what became the AMP, and `fail` is another one.

Is it written up somewhere why pattern match failure in 'do' is a 'fail' but
pattern match failure elsewhere is just pattern match failure?

Malcolm Wallace mentioned that it convenient when writing parsers, and his
example is indeed neat, but is has someone done a more substantial
investigation the benefits of this special case?

AFAIK, it was done this way for list comprehensions.  Currently, you can write e.g.

> rights = [x | Right x <- listOfEithers]

and it works properly. The list comprehension is desugared to

> do { Right x <- listOfEithers; return x }

which becomes

> do { listOfEithers >>= \l -> case l of { Right x -> return x; _ -> fail "location" }}

since the Monad instance for lists defines fail = const [], everything works out.  The nearly-equivalent code,

> listOfEithers >>= \(Right x) -> return x

indeed results in a PatternMatchFail exception.  The difference is that in the former case, the fail method is called in lieu of the remainder of the do-expression, whereas the lambda pattern match failure calls throw.  Throwing an exception wouldn't work for this because they can't be caught outside IO.

(I think this applies to generalized monad comprehensions as well, although I've never tried that)

_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

Adam Vogt
In reply to this post by David Luposchainsky
On Mon, Dec 16, 2013 at 5:26 PM, David Luposchainsky
<[hidden email]> wrote:

> Desugaring is then changed to the following:
>
> ```haskell
> -- Explicitly 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
> ```

Hello David,

GHC can already do this for you. Only `f' below has no MonadFail in
the inferred type:

{-# LANGUAGE RebindableSyntax #-}
import Prelude hiding (fail)
class MonadFail m where fail :: String -> m a
f x = do x <- x; x
g x = do Just y <- return Nothing; x
h x = do (a, Just b) <- x; a

A specification for "pattern can fail given input that is fully
defined" outside of ghc might be
<http://hackage.haskell.org/package/applicative-quoters-0.1.0.8/docs/src/Control-Applicative-QQ-ADo.html#failingPattern>.
Otherwise I suppose section "3.17.2" of the 2010 report covers this
case.

Regards,
Adam
_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

Andreas Abel
In reply to this post by David Luposchainsky
On 16.12.2013 23:57, David Luposchainsky wrote:

> On 16.12.2013 23:39, Andreas Abel wrote:
>> "Only one data constructor" should be understood hereditarily; the
>> description is not entirely accurate:
>>
>> -- Only one data constructor: do not add MonadFail constraint
>> do (Only x) <- computation     >>>     let f (Only x) = more
>>     more                        >>>     in  computation >>= f
>>
>> (Only x) should be pat such that every constructor in pat is an
>> "only-one" constructor.
>
> Do you mean in case patterns of nested "Only" types appear, like
> `Only (Only' x)`? I hadn't thought of that, good point.

Yes; its easy to give a grammar for "only-one" patterns:

   p ::= x              (variable)
       | C p1 ... pn    (C is the only constructor of its data/newtype)

>>> - The case of one data constructor should emit a warning if the data
>>> type is defined via `data`: adding another data constructor can make
>>> patterns in unrelated modules refutable.
>>
>> I don't understand the restriction "is defined via data" since I am not
>> aware of defining constructors outside of data or with something other
>> than the data keyword.  Please clarify.
>
> I meant "data and not newtype". If "Only" is a newtype data constructor,
> the pattern is irrefutable by design, is it not?

Of course, I tend to forget about newtype --- in my mind this is just a
data with a single-field single constructor.  I never understood why
there is a separate "newtype" syntax when the automatic inference of
"being a newtype" is so trivial.  There may be some obscure reason like
relying on a specific internal memory layout of data types in foreign
function interfacing or so...

Cheers,
Andreas

--
Andreas Abel  <><      Du bist der geliebte Mensch.
_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

Andreas Abel
In reply to this post by Roman Cheplyaka-2
On 17.12.2013 00:32, Roman Cheplyaka wrote:

> * David Luposchainsky <[hidden email]> [2013-12-16 23:57:19+0100]
>>> I don't understand the restriction "is defined via data" since I am not
>>> aware of defining constructors outside of data or with something other
>>> than the data keyword.  Please clarify.
>>
>> I meant "data and not newtype". If "Only" is a newtype data constructor,
>> the pattern is irrefutable by design, is it not?
>
> One notable case of one-constructor types defined not via data is
> tuples. You certainly don't want warnings for that!

Should not tuple types be understood as

   data (,) a b = (,) a b
   data (,,) a b c = (,,) a b c

making them one-constructor types?!

> Regarding newtypes vs data, I'm not so sure it should make a difference.
> It's customary to make one-field types newtypes, and then, if one
> realizes that more fields are needed, turn them into proper data types.

I agree.  What operational semantics is concerned (this mean the
execution model of Haskell in the programmer's mind),

   newtype = data

Cheers,
Andreas

--
Andreas Abel  <><      Du bist der geliebte Mensch.

_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

Roman Cheplyaka-2
* Andreas Abel <[hidden email]> [2013-12-17 10:53:21+0100]

> On 17.12.2013 00:32, Roman Cheplyaka wrote:
> >* David Luposchainsky <[hidden email]> [2013-12-16 23:57:19+0100]
> >>>I don't understand the restriction "is defined via data" since I am not
> >>>aware of defining constructors outside of data or with something other
> >>>than the data keyword.  Please clarify.
> >>
> >>I meant "data and not newtype". If "Only" is a newtype data constructor,
> >>the pattern is irrefutable by design, is it not?
> >
> >One notable case of one-constructor types defined not via data is
> >tuples. You certainly don't want warnings for that!
>
> Should not tuple types be understood as
>
>   data (,) a b = (,) a b
>   data (,,) a b c = (,,) a b c
>
> making them one-constructor types?!
You may think of it this way. But for the purposes of this proposal,
tuples shouldn't be treated like data types, because there's no
possibility they will every be extended with more constructors.

> >Regarding newtypes vs data, I'm not so sure it should make a difference.
> >It's customary to make one-field types newtypes, and then, if one
> >realizes that more fields are needed, turn them into proper data types.
>
> I agree.  What operational semantics is concerned (this mean the
> execution model of Haskell in the programmer's mind),
>
>   newtype = data

Not really. There's a semantic difference (both in operational and
denotational semantics) — see http://www.haskell.org/haskellwiki/Newtype

Not that this is relevant for this discussion.

Roman

_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries

signature.asc (853 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

Tom Ellis
In reply to this post by John Lato-2
On Mon, Dec 16, 2013 at 04:47:42PM -0800, John Lato wrote:

> On Mon, Dec 16, 2013 at 2:54 PM, Tom Ellis <
> [hidden email]> wrote:
>
> > On Mon, Dec 16, 2013 at 11:26:39PM +0100, David Luposchainsky wrote:
> > > I feel like there are a couple of elephants in the room that are sort of
> > > important but nobody really addresses them directly. One of them was
> > > what became the AMP, and `fail` is another one.
> >
> > Is it written up somewhere why pattern match failure in 'do' is a 'fail'
> > but
> > pattern match failure elsewhere is just pattern match failure?
> >
> > Malcolm Wallace mentioned that it convenient when writing parsers, and his
> > example is indeed neat, but is has someone done a more substantial
> > investigation the benefits of this special case?
>
> AFAIK, it was done this way for list comprehensions.  Currently, you can
> write e.g.
>
> > rights = [x | Right x <- listOfEithers]
>
> and it works properly.
[...]

Sure, but that then just raises the question "why was this special case
added for list comprehensions"?

To put it another way, if the original implementation of comprehensions and
do had treated pattern match failure just as pattern match failure, would a
proposal to switch to a 'fail' semantics stand a chance of being accepted?
I'm not so sure.  Admittedly it allows for some neat code, but it really is
a very special case.

Tom
_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

David Luposchainsky
On 17.12.2013 14:09, Tom Ellis wrote:

>> AFAIK, it was done this way for list comprehensions.  Currently, you can
>> write e.g.
>>
>>> rights = [x | Right x <- listOfEithers]
>>
>> and it works properly.
>
> Sure, but that then just raises the question "why was this special case
> added for list comprehensions"?

List comprehensions are special.

- They have their own semantics in the report. Desugaring them to `do`
notation is a possible implementation, but I think GHC doesn't do that
for performance reasons (-XMonadComprehensions changes this).

- List comprehensions can always rely on the empty list to exist, there
is no analogon to this in an arbitrary monad. Translations to `do`
notation should leave semantics invariant.

David
_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

Nils Anders Danielsson-4
In reply to this post by David Luposchainsky
On 2013-12-16 23:26, David Luposchainsky wrote:
> -- Explicitly irrefutable pattern: do not add MonadFail constraint
> do ~pat <- computation     >>>     let f pat = more
>    more                    >>>     in  computation >>= f

Why not "f ~pat = more"?

--
/NAD
_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

Dan Doel
In reply to this post by David Luposchainsky
On Tue, Dec 17, 2013 at 8:18 AM, David Luposchainsky
<[hidden email]> wrote:
> List comprehensions are special.
>
> - They have their own semantics in the report. Desugaring them to `do`
> notation is a possible implementation, but I think GHC doesn't do that
> for performance reasons (-XMonadComprehensions changes this).

List comprehensions weren't special (i.e. specific to lists) in
Haskell 1.4. They worked like monad comprehensions. Then they were
changed to only work with lists in Haskell98 (and that is still the
way the standard is), which is why you need to enable an extension to
get monad comprehensions now (that it has been re-implemented).

And comprehensions, of course, have failure as a built-in notion, due
to guards. This was handled by MonadZero, as was pattern match
failure.

There was also a definition of unfailable patterns. A pattern was
unfailable if it could not be refuted except possibly by partially
defined values, as I recall. This makes irrefutable patterns trivially
unfailable, if you wish to force the issue. And GHC pretty clearly
still implements things this way, as seen in adam vogt's mail.

-- Dan
_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Removing MonadFail from Monad

Herbert Valerio Riedel
In reply to this post by David Luposchainsky
On 2013-12-16 at 23:26:39 +0100, David Luposchainsky wrote:

[...]

> The proposal text can be found at the end of this text (for the mailman
> archives), or in pretty HTML here:
> https://github.com/quchen/articles/blob/master/monad_fail.md

For the record (as I missed this thread back in 2013), I'd be
enthusiastically +1 on this Fail-Removal-Proposal :-)

_______________________________________________
Libraries mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/libraries