Proposal: Remove the bogus MonadFail instance for ST

Michael Snoyman michael at snoyman.com
Thu Mar 15 12:19:13 UTC 2018


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/cb6d8589c83247ec96d5faa8
>>>> 2df3e93f419bbfe0:/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
>>>
>>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/libraries/attachments/20180315/0b3f747b/attachment.html>


More information about the Libraries mailing list