Discussion:
Proposal: Remove the bogus MonadFail instance for ST
David Feuer
2018-03-14 03:45:12 UTC
Permalink
I just noted that ST is an instance of MonadFail, with fail throwing
an error. This seems utterly contrary to the purpose of MonadFail! Can
we please remove this instance?

David Feuer
Andrew Martin
2018-03-14 10:44:52 UTC
Permalink
+1 on removing it.

Sent from my iPhone
Post by David Feuer
I just noted that ST is an instance of MonadFail, with fail throwing
an error. This seems utterly contrary to the purpose of MonadFail! Can
we please remove this instance?
David Feuer
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Ryan Scott
2018-03-14 13:05:12 UTC
Permalink
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
David Feuer
2018-03-14 13:41:49 UTC
Permalink
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.
Post by Ryan Scott
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
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Ryan Scott
2018-03-14 13:46:50 UTC
Permalink
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.
Post by David Feuer
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.
Post by Ryan Scott
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
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
David Feuer
2018-03-14 13:58:27 UTC
Permalink
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.
Post by Ryan Scott
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.
Post by David Feuer
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.
Post by Ryan Scott
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
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Ryan Scott
2018-03-14 14:25:37 UTC
Permalink
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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.
Post by Ryan Scott
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
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Michael Snoyman
2018-03-14 14:31:03 UTC
Permalink
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.
Post by Ryan Scott
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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.
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also simply
throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa82df3e9
3f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
David Feuer
2018-03-14 14:34:20 UTC
Permalink
That seems reasonable. But I wonder if pattern matching failure in IO do
should be allowed to slip by silently, or whether we should exclude the
otherwise-reasonable instance to catch more mistakes.
Post by Michael Snoyman
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
Post by Ryan Scott
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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.
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also simply
throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa8
2df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Michael Snoyman
2018-03-14 14:38:51 UTC
Permalink
I'd favor that decision as well, and could easily tweak my definition of
well behaved to simply be "no runtime exceptions or bottom values." My only
recommendation would be to start that as a separate proposal, as there's a
chance of more opposition to removing the IO instance than the ST instance.
I'd imagine IO will result in more breakage.
Post by David Feuer
That seems reasonable. But I wonder if pattern matching failure in IO do
should be allowed to slip by silently, or whether we should exclude the
otherwise-reasonable instance to catch more mistakes.
Post by Michael Snoyman
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
Post by Ryan Scott
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
entirely different story, and I'm sorry I accidentally mixed it in.
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also
simply throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa8
2df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
David Feuer
2018-03-14 14:42:00 UTC
Permalink
I certainly agree that is not a topic for this proposal.
Post by Michael Snoyman
I'd favor that decision as well, and could easily tweak my definition of
well behaved to simply be "no runtime exceptions or bottom values." My only
recommendation would be to start that as a separate proposal, as there's a
chance of more opposition to removing the IO instance than the ST instance.
I'd imagine IO will result in more breakage.
Post by David Feuer
That seems reasonable. But I wonder if pattern matching failure in IO do
should be allowed to slip by silently, or whether we should exclude the
otherwise-reasonable instance to catch more mistakes.
Post by Michael Snoyman
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
Post by Ryan Scott
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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
Post by Ryan Scott
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
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Joachim Breitner
2018-03-15 03:32:53 UTC
Permalink
Hi,
Post by David Feuer
That seems reasonable. But I wonder if pattern matching failure in IO
do should be allowed to slip by silently, or whether we should
exclude the otherwise-reasonable instance to catch more mistakes.
main = do
[path] <- getArgs

 do some stuff


is a fairly useful and (presumably) common idiom for small, one-off
scripts. I am leaning to keep allowing that.

Cheers,
Joachim
--
Joachim Breitner
***@joachim-breitner.de
http://www.joachim-breitner.de/
Dan Burton
2018-03-14 17:19:54 UTC
Permalink
I like the sound of Michael’s definition. Can we document it with the
MonadFail class, so that people making instances can find it easily?
To wit:

One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions."
+1

-- Dan Burton

On Wed, Mar 14, 2018 at 10:06 AM, Simon Peyton Jones via Libraries <
I like the sound of Michael’s definition. Can we document it with the
MonadFail class, so that people making instances can find it easily?
Simon
Snoyman
*Sent:* 14 March 2018 14:31
*Subject:* Re: Proposal: Remove the bogus MonadFail instance for ST
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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.
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also simply
throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa82df3e9
3f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Tikhon Jelvis
2018-03-15 01:47:06 UTC
Permalink
My intuition for MonadFail is that it lets us fail *in a way that can be
handled in the monad*. I'm not sure exactly how to formalize this into a
law, so maybe it's not an entirely coherent idea.

By this logic, [] is fine because we can write a function `handleEmpty :: a
-> [a] -> [a]` such that `fail "" >>= handleEmpty x` is the same as `return
x`. The same goes for IO with something built on top of catch. However,
this is not true for ST because we can't handle the resulting error without
unsafe IO operations.
I like the sound of Michael’s definition. Can we document it with the
MonadFail class, so that people making instances can find it easily?
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions."
+1
-- Dan Burton
On Wed, Mar 14, 2018 at 10:06 AM, Simon Peyton Jones via Libraries <
I like the sound of Michael’s definition. Can we document it with the
MonadFail class, so that people making instances can find it easily?
Simon
Snoyman
*Sent:* 14 March 2018 14:31
*Subject:* Re: Proposal: Remove the bogus MonadFail instance for ST
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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.
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also simply
throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa8
2df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Tikhon Jelvis
2018-03-15 01:49:39 UTC
Permalink
Oh, actually, the example I had with >>= violates the law already in place
for MonadFail. It would need to look like `handleEmpty x (fail s)` instead,
but I think the core idea remains.
Post by Tikhon Jelvis
My intuition for MonadFail is that it lets us fail *in a way that can be
handled in the monad*. I'm not sure exactly how to formalize this into a
law, so maybe it's not an entirely coherent idea.
a -> [a] -> [a]` such that `fail "" >>= handleEmpty x` is the same as
`return x`. The same goes for IO with something built on top of catch.
However, this is not true for ST because we can't handle the resulting
error without unsafe IO operations.
I like the sound of Michael’s definition. Can we document it with the
MonadFail class, so that people making instances can find it easily?
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions."
+1
-- Dan Burton
On Wed, Mar 14, 2018 at 10:06 AM, Simon Peyton Jones via Libraries <
I like the sound of Michael’s definition. Can we document it with the
MonadFail class, so that people making instances can find it easily?
Simon
Snoyman
*Sent:* 14 March 2018 14:31
*Subject:* Re: Proposal: Remove the bogus MonadFail instance for ST
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
entirely different story, and I'm sorry I accidentally mixed it in.
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also
simply throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa8
2df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Edward Kmett
2018-03-15 08:00:17 UTC
Permalink
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
Post by Michael Snoyman
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
Post by Ryan Scott
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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.
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also simply
throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa8
2df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Michael Snoyman
2018-03-15 08:13:16 UTC
Permalink
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`.

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`?
Post by Edward Kmett
I'm a bit less convinced about the benefits removing the instance for
MonadFail (ST s).
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
Post by Michael Snoyman
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
Post by Ryan Scott
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
entirely different story, and I'm sorry I accidentally mixed it in.
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also
simply throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa8
2df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Edward Kmett
2018-03-15 08:34:32 UTC
Permalink
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.
I'm a bit less convinced about the benefits removing the instance for MonadFail (ST s).
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
* 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.
Post by Ryan Scott
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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.
Post by Ryan Scott
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
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Michael Snoyman
2018-03-15 12:19:13 UTC
Permalink
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.
Post by Michael Snoyman
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
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.
I'm a bit less convinced about the benefits removing the instance for MonadFail (ST s).
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
Post by Michael Snoyman
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
Post by Ryan Scott
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
Post by David Feuer
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.
Post by Ryan Scott
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
Post by David Feuer
Post by Ryan Scott
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.
Post by David Feuer
I am not. I think that instance is fairly legitimate, as it raises
an
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
IO exception that can be caught in IO. IO's Alternative instance is
a
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
bit shadier, but that's not a topic for this proposal either. ST is
an
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
entirely different story, and I'm sorry I accidentally mixed it in.
On Wed, Mar 14, 2018 at 9:05 AM, Ryan Scott <
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also
simply throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa8
2df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Carter Schonwald
2018-03-15 14:46:57 UTC
Permalink
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?
Post by Michael Snoyman
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
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.
Post by Michael Snoyman
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
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.
I'm a bit less convinced about the benefits removing the instance for MonadFail (ST s).
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
Post by Michael Snoyman
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
Post by Ryan Scott
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
Post by David Feuer
I expect a MonadFail instance to have a well-behaved notion of
failure
Post by David Feuer
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.
Post by Ryan Scott
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
Post by David Feuer
Post by Ryan Scott
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.
Post by David Feuer
I am not. I think that instance is fairly legitimate, as it raises
an
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
IO exception that can be caught in IO. IO's Alternative instance
is a
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
bit shadier, but that's not a topic for this proposal either. ST
is an
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
entirely different story, and I'm sorry I accidentally mixed it in.
On Wed, Mar 14, 2018 at 9:05 AM, Ryan Scott <
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also
simply throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa82df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Edward Kmett
2018-03-15 16:53:40 UTC
Permalink
As one data point re: your lens, STM offers a meaningful retry.

-Edward

On Thu, Mar 15, 2018 at 3:46 PM, Carter Schonwald <
Post by Carter Schonwald
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?
Post by Michael Snoyman
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
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.
Post by Michael Snoyman
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
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.
Post by Edward Kmett
I'm a bit less convinced about the benefits removing the instance for
MonadFail (ST s).
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
Post by Michael Snoyman
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
Post by Ryan Scott
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
Post by David Feuer
I expect a MonadFail instance to have a well-behaved notion of
failure
Post by David Feuer
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
Post by David Feuer
a sense of revulsion, but maybe users should be forced to be
explicit
Post by David Feuer
with throwIO.
On Wed, Mar 14, 2018 at 9:46 AM, Ryan Scott <
Post by Ryan Scott
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
Post by David Feuer
Post by Ryan Scott
Haddocks) is to provide a type class–directed way of desugaring
partial pattern matches in do-notation. With this in mind, the
current
Post by David Feuer
Post by Ryan Scott
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
Post by David Feuer
Post by Ryan Scott
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 <
Post by David Feuer
I am not. I think that instance is fairly legitimate, as it
raises an
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
IO exception that can be caught in IO. IO's Alternative instance
is a
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
bit shadier, but that's not a topic for this proposal either. ST
is an
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
entirely different story, and I'm sorry I accidentally mixed it
in.
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
On Wed, Mar 14, 2018 at 9:05 AM, Ryan Scott <
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also
simply throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/
cb6d8589c83247ec96d5faa82df3e93f419bbfe0:/libraries/base/
Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Jon Purdy
2018-03-20 06:36:16 UTC
Permalink
Just to add a couple cents, my informal intuition for “fail” is that I
should be able to use it to “filter” things in do-notation or a monad
comprehension:

[x | Right x <- [Right "a", Right "b", Left 3]] :: [String]
[x | Right x <- Data.Vector.fromList [Right "a", Right "b", Left 3]] ::
Vector String
[x | Right x <- Just (Left 3)] :: Maybe String

The old “fail” implementation for Data.Vector used to throw an exception,
and I nudged Bryan to accept a PR making it return an empty vector, so I
could use monad comprehensions for vectors with the same expectations as
list comprehensions. That does suggest MonadPlus as the “real” source of
the semantics I want.

But there just isn’t always a well-defined thing you can do within a given
monad with only the type of “fail”.

The fact that it raises an exception for IO is fine by me, because at least
it can be caught in IO. On the other hand, I’d look at any code that
actually catches pattern-match failure exceptions as pretty smelly. Still,
in that context, to me the ideal solution is to also throw an exception in
ST, as long as some mechanism exists for safely throwing and catching
exceptions in ST. (I don’t know how hard that would be to add.)
Post by Edward Kmett
As one data point re: your lens, STM offers a meaningful retry.
-Edward
On Thu, Mar 15, 2018 at 3:46 PM, Carter Schonwald <
Post by Carter Schonwald
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?
Post by Michael Snoyman
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
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.
Post by Michael Snoyman
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
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.
Post by Edward Kmett
I'm a bit less convinced about the benefits removing the instance for
MonadFail (ST s).
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
Post by Michael Snoyman
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
Post by Ryan Scott
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
Post by David Feuer
I expect a MonadFail instance to have a well-behaved notion of
failure
Post by David Feuer
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
Post by David Feuer
a sense of revulsion, but maybe users should be forced to be
explicit
Post by David Feuer
with throwIO.
On Wed, Mar 14, 2018 at 9:46 AM, Ryan Scott <
Post by Ryan Scott
OK. You used the phrase "utterly contrary to the purpose of
MonadFail", so I'm trying to figure out exactly what you mean
here.
Post by David Feuer
Post by Ryan Scott
Prima facie, the purpose of MonadFail (at least, as explained in
its
Post by David Feuer
Post by Ryan Scott
Haddocks) is to provide a type class–directed way of desugaring
partial pattern matches in do-notation. With this in mind, the
current
Post by David Feuer
Post by Ryan Scott
MonadFail instance for ST doesn't seem too offensive.
However, I think you have some additional property in mind that
you
Post by David Feuer
Post by Ryan Scott
feel the MonadFail ST instance runs afoul of. Do you mind
explaining
Post by David Feuer
Post by Ryan Scott
in further detail what this is? (I'm not trying to be snarky
here—I
Post by David Feuer
Post by Ryan Scott
genuinely don't know what you're getting at.)
Ryan S.
On Wed, Mar 14, 2018 at 9:41 AM, David Feuer <
Post by David Feuer
I am not. I think that instance is fairly legitimate, as it
raises an
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
IO exception that can be caught in IO. IO's Alternative instance
is a
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
bit shadier, but that's not a topic for this proposal either. ST
is an
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
entirely different story, and I'm sorry I accidentally mixed it
in.
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
On Wed, Mar 14, 2018 at 9:05 AM, Ryan Scott <
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also
simply throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa8
2df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Zemyla
2018-03-20 19:48:54 UTC
Permalink
To be honest, you could probably do safe throwing and catching in ST by
wrapping a thrown SomeException in some hidden exception type (call it
STException), not exporting that type, and then having the catch command be
like the IO version except it only catches and unwraps STException.
Post by Jon Purdy
Just to add a couple cents, my informal intuition for “fail” is that I
should be able to use it to “filter” things in do-notation or a monad
[x | Right x <- [Right "a", Right "b", Left 3]] :: [String]
Vector String
[x | Right x <- Just (Left 3)] :: Maybe String
The old “fail” implementation for Data.Vector used to throw an exception,
and I nudged Bryan to accept a PR making it return an empty vector, so I
could use monad comprehensions for vectors with the same expectations as
list comprehensions. That does suggest MonadPlus as the “real” source of
the semantics I want.
But there just isn’t always a well-defined thing you can do within a given
monad with only the type of “fail”.
The fact that it raises an exception for IO is fine by me, because at
least it can be caught in IO. On the other hand, I’d look at any code that
actually catches pattern-match failure exceptions as pretty smelly. Still,
in that context, to me the ideal solution is to also throw an exception in
ST, as long as some mechanism exists for safely throwing and catching
exceptions in ST. (I don’t know how hard that would be to add.)
Post by Edward Kmett
As one data point re: your lens, STM offers a meaningful retry.
-Edward
On Thu, Mar 15, 2018 at 3:46 PM, Carter Schonwald <
Post by Carter Schonwald
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?
Post by Michael Snoyman
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
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.
Post by Michael Snoyman
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
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.
Post by Edward Kmett
I'm a bit less convinced about the benefits removing the instance for
MonadFail (ST s).
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
Post by Michael Snoyman
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
Post by Ryan Scott
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
Post by David Feuer
I expect a MonadFail instance to have a well-behaved notion of
failure
Post by David Feuer
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
Post by David Feuer
the instance should be removed for IO as well; I don't have as
strong
Post by David Feuer
a sense of revulsion, but maybe users should be forced to be
explicit
Post by David Feuer
with throwIO.
On Wed, Mar 14, 2018 at 9:46 AM, Ryan Scott <
Post by Ryan Scott
OK. You used the phrase "utterly contrary to the purpose of
MonadFail", so I'm trying to figure out exactly what you mean
here.
Post by David Feuer
Post by Ryan Scott
Prima facie, the purpose of MonadFail (at least, as explained in
its
Post by David Feuer
Post by Ryan Scott
Haddocks) is to provide a type class–directed way of desugaring
partial pattern matches in do-notation. With this in mind, the
current
Post by David Feuer
Post by Ryan Scott
MonadFail instance for ST doesn't seem too offensive.
However, I think you have some additional property in mind that
you
Post by David Feuer
Post by Ryan Scott
feel the MonadFail ST instance runs afoul of. Do you mind
explaining
Post by David Feuer
Post by Ryan Scott
in further detail what this is? (I'm not trying to be snarky
here—I
Post by David Feuer
Post by Ryan Scott
genuinely don't know what you're getting at.)
Ryan S.
On Wed, Mar 14, 2018 at 9:41 AM, David Feuer <
Post by David Feuer
I am not. I think that instance is fairly legitimate, as it
raises an
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
IO exception that can be caught in IO. IO's Alternative
instance is a
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
bit shadier, but that's not a topic for this proposal either.
ST is an
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
entirely different story, and I'm sorry I accidentally mixed it
in.
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
On Wed, Mar 14, 2018 at 9:05 AM, Ryan Scott <
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also
simply throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa8
2df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Gershom B
2018-03-21 04:41:33 UTC
Permalink
On March 20, 2018 at 3:53:24 PM, Zemyla (***@gmail.com) wrote:
To be honest, you could probably do safe throwing and catching in ST by wrapping a thrown SomeException in some hidden exception type (call it STException), not exporting that type, and then having the catch command be like the IO version except it only catches and unwraps STException.
Indeed, Carter has such a package here: https://hackage.haskell.org/package/monad-ste-0.1.0.0

I wonder — could ST just be extended with some version of such functionality directly?

-g




On Mar 20, 2018 01:40, "Jon Purdy" <***@gmail.com> wrote:
Just to add a couple cents, my informal intuition for “fail” is that I should be able to use it to “filter” things in do-notation or a monad comprehension:

[x | Right x <- [Right "a", Right "b", Left 3]] :: [String]
[x | Right x <- Data.Vector.fromList [Right "a", Right "b", Left 3]] :: Vector String
[x | Right x <- Just (Left 3)] :: Maybe String

The old “fail” implementation for Data.Vector used to throw an exception, and I nudged Bryan to accept a PR making it return an empty vector, so I could use monad comprehensions for vectors with the same expectations as list comprehensions. That does suggest MonadPlus as the “real” source of the semantics I want.

But there just isn’t always a well-defined thing you can do within a given monad with only the type of “fail”.

The fact that it raises an exception for IO is fine by me, because at least it can be caught in IO. On the other hand, I’d look at any code that actually catches pattern-match failure exceptions as pretty smelly. Still, in that context, to me the ideal solution is to also throw an exception in ST, as long as some mechanism exists for safely throwing and catching exceptions in ST. (I don’t know how hard that would be to add.)


On Thu, Mar 15, 2018 at 9:53 AM, Edward Kmett <***@gmail.com> wrote:
As one data point re: your lens, STM offers a meaningful retry.

-Edward

On Thu, Mar 15, 2018 at 3:46 PM, Carter Schonwald <***@gmail.com> wrote:
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 <***@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 <***@gmail.com> wrote:


On Mar 15, 2018, at 9:13 AM, Michael Snoyman <***@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 <***@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 <***@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 <***@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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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.
Post by Ryan Scott
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
 http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
***@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries


_______________________________________________
Libraries mailing list
***@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries




_______________________________________________
Libraries mailing list
***@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries


_______________________________________________
Libraries mailing list
***@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

Edward Kmett
2018-03-15 08:15:07 UTC
Permalink
In particular this is distinct from an attempt like

instance MonadFail Identity where
fail = error

which already fails the left zero law as

fail s >>= f = f (error e)

but that right hand side is /= fail s for non-strict f.

-Edward
I'm a bit less convinced about the benefits removing the instance for MonadFail (ST s).
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
* 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.
Post by Ryan Scott
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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.
Post by Ryan Scott
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
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Michael Snoyman
2018-03-15 08:24:53 UTC
Permalink
FWIW, `fail = error` does create a law-abiding `MonadFail` for the strict
identity monad:

Identity x >>= f = f $! x

So the generalize argument wouldn't apply to only ST, but to a much larger
class of monads.
Post by Edward Kmett
In particular this is distinct from an attempt like
instance MonadFail Identity where
fail = error
which already fails the left zero law as
fail s >>= f = f (error e)
but that right hand side is /= fail s for non-strict f.
-Edward
I'm a bit less convinced about the benefits removing the instance for MonadFail (ST s).
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
Post by Michael Snoyman
One possible "well behaved" intuition could be "cannot result in an
exception thrown from pure code without usage of unsafe functions." By this
* 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.
Post by Ryan Scott
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
Post by David Feuer
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.
Post by Ryan Scott
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.
Post by David Feuer
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
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
entirely different story, and I'm sorry I accidentally mixed it in.
Post by Ryan Scott
It's worth noting that the MonadFail instance for IO [1] also
simply throws
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
an error (by way of failIO). Are you proposing we remove this
instance as
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
well?
Ryan S.
-----
[1]
http://git.haskell.org/ghc.git/blob/cb6d8589c83247ec96d5faa8
2df3e93f419bbfe0:/libraries/base/Control/Monad/Fail.hs#l80
Post by David Feuer
Post by Ryan Scott
Post by David Feuer
Post by Ryan Scott
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Michael Snoyman
2018-03-14 13:44:13 UTC
Permalink
+1, this seems like a good move to me.
Post by David Feuer
I just noted that ST is an instance of MonadFail, with fail throwing
an error. This seems utterly contrary to the purpose of MonadFail! Can
we please remove this instance?
David Feuer
_______________________________________________
Libraries mailing list
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Loading...