
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.