Proposal: Extensible exceptions

David Menendez dave at
Sun Jul 6 03:31:41 EDT 2008

On Sat, Jul 5, 2008 at 7:39 PM, Yitzchak Gale <gale at> wrote:
> I wrote:
>>> How does this affect integration between IO exceptions
>>> and pure exceptions, in particular Control.Monad.Error from mtl?
> Ian Lynagh wrote:
>> It might even be possible to get rid of the Error class and use the
>> Exception class instead.
> I like that idea. In practice, I always find strMsg and noMsg nothing
> more than an annoyance. What is really needed is a required Show
> instance, like the Exception class has. And of course, having all
> Exception instances available as candidates for pure exceptions
> is nice.

The Error class is cruft anyway. It only exists so that the Error and
ErrorT monads can support "fail" and "mzero". From my standpoint,
"fail" shouldn't exist, and the error monads shouldn't conflate mzero
and throwErr. But that's another topic.

> But this would be a traumatic change. There would need to be some
> migration/deprecation path.
> As Bulat points out, there will be a lot of pain caused even
> for Exception itself. The fact that it is easy to figure out
> how to fix code to make it work again (assuming that is true)
> will not change the fact that many, many programs will
> no longer compile. Past experience shows that this
> causes a lot of damage.
> To get there with less pain, I think we should:
> o For 6.10, make the new Exceptions available so that
>  everyone can start working on switching, but leave old
>  Exceptions as the default so that existing programs still
>  work. Prominently mark old exceptions as deprecated
>  in all documentation.
> o In the next version, make the new Exceptions the default.
>  Make sure that programs using new Exceptions for 6.10
>  will still work (e.g., leave NewException as an alias for
>  Exception, or whatever).

I have the guts of an extensible exception library modeled on Simon
Marlow's paper that can coexist with the current Control.Exception
regime. That is, exceptions thrown by "old" code can be caught by
"new" code, and vice versa.

The trick is adding two secret methods to the Exception class which
handle conversion to and from Control.Exception.Exception.

> import qualified Control.Exception as Legacy

> class (Show e, Typeable e) => Exception e where
>     toException         :: e -> SomeException
>     fromException       :: SomeException -> Maybe e
>     toLegacyException   :: e -> Legacy.Exception
>     fromLegacyException :: Legacy.Exception -> Maybe e
>     toException = SomeException
>     fromException (SomeException e) = cast e
>     toLegacyException = Legacy.DynException . toDyn . toException
>     fromLegacyException (Legacy.DynException d)
>         = fromDynamic d >>= fromException
>     fromLegacyException _ = Nothing

Adding new exceptions works exactly as it does in Simon's paper.

The wrappers for already existing exceptions silently translate into
their current representations.

> data DivideByZero = DivideByZero deriving (Show, Typeable)
> instance Exception DivideByZero where
>     toException = SomeException . toLegacyException
>     toLegacyException DivideByZero
>         = Legacy.ArithException Legacy.DivideByZero
>     fromException (SomeException e) = cast e >>= fromLegacyException
>     fromLegacyException (Legacy.ArithException Legacy.DivideByZero)
>         = Just DivideByZero
>     fromLegacyException _ = Nothing

This code implements the new exceptions on top of the old exceptions.
Eventually, once the new exceptions have gained acceptance, the
internals of the library can be changed to implement the old
exceptions on top of the new exceptions.

I only ever made a partial implementation, but I can post it if anyone
is interested.

Dave Menendez <dave at>

More information about the Libraries mailing list