wiki:ExtensibleExceptions

Version 2 (modified by john@…, 8 years ago) (diff)

--

Extensible Exceptions

The main annoyance is that if you want to throw anything except a String or an IOError (or maybe one or two other types), you have to use throwDyn, catchDyn, etc. Annoying, and can be complicated for newbies. Both Python and Java leverage OOP so that exceptions are classes and can be subclassed for greater precision. Java even enforces this to a greater degree. I don't know how possible this is in Haskell, but it is a great feature as far as I'm concerned. It would also be helpful to force all exceptions to provide a Show instance, so that generic handlers would be more easily possible.

Proposals 2 & 3 below solve this problem, and are not specific to IO exceptions, nor do they require general exceptions.

IO Exceptions

Exceptions in the IO monad are rather odd and warty. A lot of effort was put into making IOError an abstract type with lots of accessor functions presumably with the intent that implementations could extend it, but it has been interpreted that extensions might change the behavior of existing (though unlikely) haskell programs so it has not been done. note that this has nothing to do with ImpreciseExceptions.

See also

Proposal 1

declare that IOError is extendable in the report, and that the behavior of catches that do not either rethrow the error or use a conditional (such as in catchJust) to limit the scope of the catch have undefined behavior.

Pros

  • already implemented by definition by all haskell 98 compilers
  • that effort making IOError abstract won't be wasted

Cons

  • exceptions are only implementation extendable and not user extendable.

Proposal 2

Use Data.Dynamic to make exceptions user and implementation extendable.

the types would become:

catch :: Typeable b => IO a -> (b -> IO a) -> IO a
ioError :: Typebale b => b -> IO a

there is the strong advantage that this necessarily gets rid of the unconstrained catch since b's type must be resolved at compile time.

however this does create a situation where 'bracket' and 'finally' cannot be implemented with just 'catch' so back door routines

catchDynamic :: IO a -> (Dynamic -> IO a) -> IO a
ioErrorDynamic :: Dynamic -> IO a

would be added with a note saying that the behavior is undefined unless the catching routine rethrows the dynamic exception. an alternative would be to make 'bracket' and 'finally' built-in.

Pros

  • User extendable
  • If IOError were made an instance of typeable, it is completly backwards compatable.
  • unconstrained catches disallowed

Cons

  • Would need Data.Typeable and Data.Dynamic to be part of the language (not a bad idea by itself though)
  • Also requires pattern guards.
  • unconstrainted catches disallowed

Proposal 3

A slight modification of Proposal 2, we provide an Exception class that wraps both Show and Typeable:

class (Typeable a, Show a) => Exception a

To make an existing type into one that can be thrown/caught as an exception, it is just necessary to derive Typeable and Show, and say 'instance Exception T'. (we could also allow the Exception class to be derived).

Some example code that works with GHC is here: prototype.