Proposal: Remove the bogus MonadFail instance for ST

Carter Schonwald carter.schonwald at gmail.com
Thu Mar 15 14:46:57 UTC 2018


So this boils down to two concerns

1) should st support refutable pattern matches , and this in turn touches
on pure exceptions and totality concerns

2) is monad fail actually the monad zero or just support for refutable
patterns , which may sometimes use monad zero for implementation?

I’m not sure one way or another.

One lens for this is: how do the arguments for monad fail differ between ST
and STM?


On Thu, Mar 15, 2018 at 8:22 AM Michael Snoyman <michael at snoyman.com> wrote:

> I also find your `Point` data type telling, but I think for the opposite
> reason. I think most people would want to avoid letting a pattern match
> silently turn into a bottom value in the `Point` data type.
>
> IMO, what all of this comes down to is the fact that `MonadFail` is being
> used in this thread for two purposes:
>
> 1. By you to be the general purpose zero class
> 2. By (I think) everyone else to be the class that allows you to do
> refutable pattern matches
>
> Personally, I think `fail :: String -> m a` is a bad type for a general
> purpose zero class; either MonadZero, or a type class using `Exception`
> like `MonadThrow` in `exceptions, would be better. And regardless, I don't
> think we should be encouraging further usage of bottom values, even if the
> usage of a bottom is in fact law abiding.
>
> On Thu, Mar 15, 2018 at 10:34 AM, Edward Kmett <ekmett at gmail.com> wrote:
>
>>
>>
>> On Mar 15, 2018, at 9:13 AM, Michael Snoyman <michael at snoyman.com> wrote:
>>
>> If the concern is a lack of ability to have the properly sequenced
>> exception throwing, I would argue that the correct response is to provide a
>> monomorphic `failST :: String -> ST s a` function to be explicit about the
>> purpose. I'd personally go farther and make the function `throwST ::
>> Exception e => e -> ST s a`.
>>
>>
>> I definitely agree here.
>>
>> While it's true that `MonadFail (ST s)` obeys the laws, the point here is
>> about the extra functionality provided by `MonadFail`, namely around
>> pattern matching. I think the question can be boiled down to: do we want to
>> make it easy to call `fail` when writing code inside `ST`?
>>
>>
>> My point was more that this is rather distinct from the other cases
>> mentioned in that it is a true legal instance, enabling things like a
>> fail-based guard to actually protect against subsequent code in ST
>> executing.
>>
>> I do find it telling that we can get into a similar situation completely
>> without effects with
>>
>> data Point a = Point a
>>
>> ...
>>
>> instance Monad Point where
>>   return = Point
>>   Point a >>= f = f a
>>
>> instance MonadFail Point where
>>   fail = error
>>
>> the extra "point" added by using data rather than newtype and the strict
>> pattern match in >>= plumbs the error out in the same fashion as ST here.
>>
>> I find the ability to explicitly construct bottoms at the right time to
>> guard subsequent operations in those monads to be a piece of vocabulary
>> that would be otherwise missing if we retroactively tried to impose some
>> additional handling laws that aren't required by having a cancellative zero.
>>
>> On Thu, Mar 15, 2018 at 10:00 AM, Edward Kmett <ekmett at gmail.com> wrote:
>>
>>> I'm a bit less convinced about the benefits removing the instance for
>>> MonadFail (ST s).
>>>
>>> Playing devil's advocate here:
>>>
>>> Recall that throwIO is distinct from throw for a good reason, as it
>>> ensures that the throwing occurs at the right step in the sequence of binds.
>>>
>>> The `fail` instance for ST can similarly be viewed as a perfectly
>>> reasonable monotone function affecting the result of runST :: (forall s. ST
>>> s a) -> a, which produces an `a` that is the appropriate bottom at the
>>> right time when you take a certain branch in the ST calculation. This is
>>> rather different than Identity, as you can't just ape this behavior by
>>> calling 'error' instead as you need the smarter call.
>>>
>>> To achieve that functionality today _without_ fail, you need to reach
>>> for unsafe operations `unsafeIOtoST . failIO` it to get the correct
>>> semantics, which is a damn sight messier and scarier and importantly
>>> removing the instance means this can't be something that is done by just
>>> delegating to base monad transformer 'fail' as would be done through
>>> something like `StateT s (ST s')`. This seems to create a false tension
>>> between doing the most defined thing and doing the thing I want with a
>>> stronger constraint, which I usually take as a sign that the building
>>> blocks are wrong.
>>>
>>> Removing this instance comes at a real cost in terms of generality of
>>> code that uses `MonadFail`:  It does pass the left zero law!
>>>
>>> Overall, I'm -1, as I'm actually leaning against the removal of the
>>> instance personally on the grounds above.
>>>
>>> -Edward
>>>
>>> On Wed, Mar 14, 2018 at 3:31 PM, Michael Snoyman <michael at snoyman.com>
>>> wrote:
>>>
>>>> One possible "well behaved" intuition could be "cannot result in an
>>>> exception thrown from pure code without usage of unsafe functions." By this
>>>> definition:
>>>>
>>>> * Maybe's fail is well behaved: using `fail "foo"` results in a total
>>>> Nothing value
>>>> * List's: same thing, but with an empty list
>>>> * IO: runtime exception, but the exception is _not_ in pure code, but
>>>> rather from within IO, where exceptions are always to be expected
>>>> * ST: `runST (fail "foo")` results in a pure value which, when
>>>> evaluated, throws a runtime exception, breaking the well behaved definition
>>>> * Identity: `Identity (fail "foo")` can only be a pure value which
>>>> throws an exception, and is therefore not well behaved
>>>>
>>>> Note that I added the requirement of "without usage of unsafe
>>>> functions," since `unsafePerformIO (fail "foo")` can result in a pure
>>>> bottom value.
>>>>
>>>> On Wed, Mar 14, 2018 at 4:25 PM, Ryan Scott <ryan.gl.scott at gmail.com>
>>>> wrote:
>>>>
>>>>> Thanks, that makes more sense. I'm inclined to agree that MonadFail
>>>>> instances should fail in a "well-behaved" way. (I wish I knew how to
>>>>> make the phrase "well-behaved" more formal, but I don't.) It might be
>>>>> worth adding this intuition to the Haddocks for MonadFail.
>>>>>
>>>>> That being said, one thing to consider before removing this instance
>>>>> is that there will be some breakage. Ben Gamari added this instance in
>>>>> [1] because apparently the regex-tdfa package needed it. Other than
>>>>> that, though, I don't have any real objections to removing this
>>>>> instance.
>>>>>
>>>>> Ryan S.
>>>>> -----
>>>>> [1] https://phabricator.haskell.org/D3982
>>>>>
>>>>> On Wed, Mar 14, 2018 at 9:58 AM, David Feuer <david.feuer at gmail.com>
>>>>> wrote:
>>>>> > I expect a MonadFail instance to have a well-behaved notion of
>>>>> failure
>>>>> > within the monad. An exception from "pure" code (which is what ST
>>>>> > simulates) is not that. On the other hand, perhaps you're right and
>>>>> > the instance should be removed for IO as well; I don't have as strong
>>>>> > a sense of revulsion, but maybe users should be forced to be explicit
>>>>> > with throwIO.
>>>>> >
>>>>> > On Wed, Mar 14, 2018 at 9:46 AM, Ryan Scott <ryan.gl.scott at gmail.com>
>>>>> wrote:
>>>>> >> OK. You used the phrase "utterly contrary to the purpose of
>>>>> >> MonadFail", so I'm trying to figure out exactly what you mean here.
>>>>> >> Prima facie, the purpose of MonadFail (at least, as explained in its
>>>>> >> Haddocks) is to provide a type class–directed way of desugaring
>>>>> >> partial pattern matches in do-notation. With this in mind, the
>>>>> current
>>>>> >> MonadFail instance for ST doesn't seem too offensive.
>>>>> >>
>>>>> >> However, I think you have some additional property in mind that you
>>>>> >> feel the MonadFail ST instance runs afoul of. Do you mind explaining
>>>>> >> in further detail what this is? (I'm not trying to be snarky here—I
>>>>> >> genuinely don't know what you're getting at.)
>>>>> >>
>>>>> >> Ryan S.
>>>>> >>
>>>>> >> On Wed, Mar 14, 2018 at 9:41 AM, David Feuer <david.feuer at gmail.com>
>>>>> wrote:
>>>>> >>> I am not. I think that instance is fairly legitimate, as it raises
>>>>> an
>>>>> >>> IO exception that can be caught in IO. IO's Alternative instance
>>>>> is a
>>>>> >>> bit shadier, but that's not a topic for this proposal either. ST
>>>>> is an
>>>>> >>> entirely different story, and I'm sorry I accidentally mixed it in.
>>>>> >>>
>>>>> >>> On Wed, Mar 14, 2018 at 9:05 AM, Ryan Scott <
>>>>> ryan.gl.scott at gmail.com> wrote:
>>>>> >>>> It's worth noting that the MonadFail instance for IO [1] also
>>>>> simply throws
>>>>> >>>> an error (by way of failIO). Are you proposing we remove this
>>>>> instance as
>>>>> >>>> well?
>>>>> >>>>
>>>>> >>>> Ryan S.
>>>>> >>>> -----
>>>>> >>>> [1]
>>>>> >>>>
>>>>> http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa82df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
>>>>> >>>>
>>>>> >>>> _______________________________________________
>>>>> >>>> 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
>>>>>
>>>>
>>>>
>>>> _______________________________________________
>>>> 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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/libraries/attachments/20180315/c373842d/attachment.html>


More information about the Libraries mailing list