Proposal: Extensible exceptions

Isaac Dupree isaacdupree at charter.net
Mon Jul 7 13:51:11 EDT 2008


I have type issues.  Look how inconsistent these types are (I think, 
copied from the patch); some use forall and some use SomeException:
catchAny :: IO a -> (forall e . Exception e => e -> IO a) -> IO a
setUncaughtExceptionHandler :: (SomeException -> IO ()) -> IO ()
getUncaughtExceptionHandler :: IO (SomeException -> IO ())

Obviously we should have
catchAny :: IO a -> (forall e . Exception e => e -> IO a) -> IO a
setUncaughtExceptionHandler :: (forall e . Exception e => e -> IO ()) -> 
IO ()
getUncaughtExceptionHandler :: IO (forall e . Exception e => e -> IO ())

But that requires some kind of impredicative types for 
getUncaughtExceptionHandler.
Then, instead, for consistency, obviously we should have
catchAny :: IO a -> (SomeException -> IO a) -> IO a
setUncaughtExceptionHandler :: (SomeException -> IO ()) -> IO ()
getUncaughtExceptionHandler :: IO (SomeException -> IO ())
Then we don't even need the Rank2Types extension?

Also, according to the extensible exceptions paper p. 4 (footnote 3), 
`catch` with SomeException type should suffice, such that catchAny is 
not needed?  Or was it decided that the facility to catch SomeException 
should be separated from the facility to catch any more-specific group 
of exceptions (since that implementation in the paper looks like a bit 
of a hack... or perhaps to warn people that it's a bad idea and should 
use a function with a name that's a big flashing warning)?



On a different note: What about strictness?  I'll take an arbitrary 
example from the paper

data SomeFloatException
   = forall a . (Exception a) => SomeFloatException a
   deriving Typeable

Then, (SomeException (SomeArithException (undefined :: 
SomeFloatException))) is not _|_.  I think it's generally a bad idea to 
throw exceptions that contain _|_ in their values; it would usually be 
just as good to evaluate their values first and if they're _|_, let that 
be the exception instead.  In this particular case, should the 
convention for defining nodes in the exception hierarchy, have a 
strictness annotation, such as the following?

data SomeFloatException
   = forall a . (Exception a) => SomeFloatException !a
   deriving Typeable

data SomeArithException
   = forall a . (Exception a) => SomeArithException !a
   deriving Typeable

data SomeException
   = forall a . (Exception a) => SomeException !a
   deriving Typeable

(Since newtype won't work for existentials, we can't use it here.)

This flattens the hierarchy out of the way affecting the semantics, 
while still allowing actual exception types to be lazy if they want to 
be, e.g.

data DivideByZero = DivideByZero  --(a non-lazy error with no variables)
   deriving (Typeable, Show)
data ErrorCall = ErrorCall String  --not explicitly strict in the string
   deriving (Typeable, Show)

This way (error (error ("abc"++error ...))) still works :-P.  More 
seriously of a reason lack of strictness was annoying was 
Debug.Trace.trace interleaving, but that's unsafePerformIO business that 
can be changed separately, and has not much to do with exceptions.  Or, 
error messages that 'show' arguments that weren't already evaluated, and 
have errors themselves.. that's happened to me :-)


-Isaac


More information about the Libraries mailing list