
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