On Tue, Oct 19, 2010 at 3:34 AM, Sterling Clover <s.clover at gmail.com> wrote:
> On Mon, Oct 18, 2010 at 8:50 AM, Michael Snoyman <michael at snoyman.com> 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.