Re: MonadCatchIO, finally and the error monad

Thanks for you reply, Michael. See inline ... On Mon, Oct 18, 2010 at 11:21 AM, Michael Snoyman
As an haskell newbie, I've got some questions on this matter.
Is it using ErrorT instead of extensible exeptions really necessary? I've read your comment stating that we cannot pass function arguments to Exceptions because of the Show class constraint. But is that really limiting? Wouldn't your use case (e.g. HTTP redirection in Yesod) be implementable with extensible exceptions?
I would guess that the rule of thumb would be not to mix extensible exception and ErrorT whenever it is possibile.
I've quickly read your post on inverting transformer stacks and, from my newbie point of view, I feel that the extra complexity isn't worth the gain.
I'm not sure why you think inverting the ErrorT monad is more complicated than MonadCatchIO: they're both doing basically the same thing. The difference is that MonadInvertIO does this generically for a whole class of functions, and therefore does it right.
I could have written MonadCatchIO, I could not have written InvertIO :-)
Anyway, it *is* theoretically possible to use extensible exceptions anywhere you would use ErrorT, but there's always a runtime hit in using runtime exceptions. Also, it's a difference in philosophy: coming from a language like Python or Ruby, it may feel natural to use exceptions for these kinds of cases. In Haskell, we try to make exceptions respect their name, and only use them in exceptional circumstances.
Is the performance penalty of using runtime exception so high? I would sacrifice some bits of performance for a more consistent error reporting / flow control. For the argument that Exceptions are for exceptional cases I'm totally with David MacIver [1]. I perfectly see the point of using ErrorT, but not when you need to handle runtime exceptions as well. As an haskell newbie I think that _THIS_, error handling strategies, is the single most troublesome point where some consistency (at least as a best practice) is really needed. I had hopes that extensible execptions could clarify the issue raised by [2] at least in "new" code... Thank again Paolo [1] http://www.drmaciver.com/2009/03/exceptions-for-control-flow-considered-perf... [2] http://www.randomhacks.net/articles/2007/03/10/haskell-8-ways-to-report-erro...

On Mon, Oct 18, 2010 at 2:14 PM, Paolo Losi
Thanks for you reply, Michael. See inline ...
On Mon, Oct 18, 2010 at 11:21 AM, Michael Snoyman
As an haskell newbie, I've got some questions on this matter.
Is it using ErrorT instead of extensible exeptions really necessary? I've read your comment stating that we cannot pass function arguments to Exceptions because of the Show class constraint. But is that really limiting? Wouldn't your use case (e.g. HTTP redirection in Yesod) be implementable with extensible exceptions?
I would guess that the rule of thumb would be not to mix extensible exception and ErrorT whenever it is possibile.
I've quickly read your post on inverting transformer stacks and, from my newbie point of view, I feel that the extra complexity isn't worth the gain.
I'm not sure why you think inverting the ErrorT monad is more complicated than MonadCatchIO: they're both doing basically the same thing. The difference is that MonadInvertIO does this generically for a whole class of functions, and therefore does it right.
I could have written MonadCatchIO, I could not have written InvertIO :-)
The typeclass itself is simple. It's the *instances* that are complicated. At least I find InvertIO to be not much more complicated on the instance side.
Anyway, it *is* theoretically possible to use extensible exceptions anywhere you would use ErrorT, but there's always a runtime hit in using runtime exceptions. Also, it's a difference in philosophy: coming from a language like Python or Ruby, it may feel natural to use exceptions for these kinds of cases. In Haskell, we try to make exceptions respect their name, and only use them in exceptional circumstances.
Is the performance penalty of using runtime exception so high? I would sacrifice some bits of performance for a more consistent error reporting / flow control. For the argument that Exceptions are for exceptional cases I'm totally with David MacIver [1]. I perfectly see the point of using ErrorT, but not when you need to handle runtime exceptions as well. As an haskell newbie I think that _THIS_, error handling strategies, is the single most troublesome point where some consistency (at least as a best practice) is really needed. I had hopes that extensible execptions could clarify the issue raised by [2] at least in "new" code...
The point here is that we want to treat these things very differently. Just take a look at the runHandler code in the Yesod.Handler module: runtime exceptions need to be wrapped up in an internal server error, whereas "Left" values from the ErrorT get "case"d on. I understand that all of this could be done with extensible exceptions, but that's really just taking something which is currently explicit and type-checkable and making it implicit. It really does seem to me to be a typical dynamic-versus-static issue. Michael

On Mon, Oct 18, 2010 at 2:50 PM, Michael Snoyman
It really does seem to me to be a typical dynamic-versus-static issue.
I got your point. Thanks for taking the time to explain the difference between the options. Grazie! Paolo

On Mon, Oct 18, 2010 at 8:50 AM, Michael Snoyman
The point here is that we want to treat these things very differently. Just take a look at the runHandler code in the Yesod.Handler module: runtime exceptions need to be wrapped up in an internal server error, whereas "Left" values from the ErrorT get "case"d on. I understand that all of this could be done with extensible exceptions, but that's really just taking something which is currently explicit and type-checkable and making it implicit. It really does seem to me to be a typical dynamic-versus-static issue.
I fail to see how this is a dynamic-static issue. You're already forced to catch exceptions and wrap them in a MLeft, and then later you force the MLeft and MRight values in to a uniform representation which you then case on. Catching a special exception type for exit and forcing it into the same ultimate union representation doesn't seem conceptually any more difficult, and in fact removes the need to reason about two types of exceptions throughout the rest of the code base. I've got a large project that is based on a transformer stack over IO, and one of my ongoing regrets has been that I went with Either as well as extensible exceptions -- there's never been a genuine payoff, and there have been more than a few headaches. Cheers, Sterl.

On Tue, Oct 19, 2010 at 3:34 AM, Sterling Clover
On Mon, Oct 18, 2010 at 8:50 AM, Michael Snoyman
wrote: The point here is that we want to treat these things very differently. Just take a look at the runHandler code in the Yesod.Handler module: runtime exceptions need to be wrapped up in an internal server error, whereas "Left" values from the ErrorT get "case"d on. I understand that all of this could be done with extensible exceptions, but that's really just taking something which is currently explicit and type-checkable and making it implicit. It really does seem to me to be a typical dynamic-versus-static issue.
I fail to see how this is a dynamic-static issue. You're already forced to catch exceptions and wrap them in a MLeft, and then later you force the MLeft and MRight values in to a uniform representation which you then case on. Catching a special exception type for exit and forcing it into the same ultimate union representation doesn't seem conceptually any more difficult, and in fact removes the need to reason about two types of exceptions throughout the rest of the code base.
I've got a large project that is based on a transformer stack over IO, and one of my ongoing regrets has been that I went with Either as well as extensible exceptions -- there's never been a genuine payoff, and there have been more than a few headaches.
Yesod has used this approach for a long while now, and the only issue has been that buggy "finally" function in MonadCatchIO. I fail to see what's so complicated here, to be honest. If I were to switch, I'd then need to: * Create an Exception instance for HCContent, a datatype which is most definitely *not* an exception. * Start using Typeable to parse out the HCContent stuff from all other exception types. I don't see it shortening the code at all, and it's conceptually wrong: redirection is not an exception. Oh, and I just remembered the reason why it absolutely *can't* work: consider this real life code from haskellers.com: Just uid -> debugRunDB $ do u <- get404 uid mun <- getBy $ UniqueUsernameUser uid case mun of Nothing -> return (uid, u) Just (_, Username _ un) -> lift $ redirect RedirectPermanent $ UserR un This whole block runs as a database transaction (initiated by debugRunDB), and checks to see if a user has a username. If so, the browser is redirected to a URL with the username instead of the user ID. If I used extensible exceptions instead of an Either monad, then that last line would throw an exception, which would cause the database transaction to rollback instead of commit, which is decidedly *not* what we want here[1]. This stresses my point of using the right tool for the right job: you can often be surprised by the strange interactions that will occur when you use something in a way it wasn't intended. Michael [1] True, this database action doesn't actually make any changes, but it's very easy to imagine a case where it would, eg logging.
participants (3)
-
Michael Snoyman
-
Paolo Losi
-
Sterling Clover