
On Sat, Jul 5, 2008 at 7:39 PM, Yitzchak Gale
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