On Tue, Apr 21, 2015 at 6:40 PM, davean <davean@xkcd.com> wrote:
So, I've had a number of issues with exceptions. This has been one of them. I don't really like this proposal as it stands though as it seems to make catch a specific exception with said extra info more difficult.

This is data Control.Exception can move around on its own though, right? The problem really isn't passing it internal, we could just make a (Stack, SomeException) tuple just fine, in theory I think (I'll admit I've not actually reviewed the code, and this isn't meant as a complete proposal but more a thought experiment). The problem is code handling the data and working with old code while not losing any of the power of the current system.

So we start with: catch :: Exception e => IO a -> (e -> IO a) -> IO a

Now this proposal allows: catch :: IO a -> (SomeException -> IO a) -> IO a
If we want access to the new information, but that's not really satisfactory.

Real code regularly wants to (picking an arbitrary instance of Exception) do: catch :: IO a -> (IOError -> IO a) -> IO a 
only we still want new data.
 
There is no way to always pass around the new data without breaking the Control.Exception API or having users add extra fields to their data types.  This is a fundamental issue, and one that my proposal does not seek to address.  Infact, I acknowledge it at the end - fromException now loses data.  To me it is quite acceptable because:

* This is a fundamental limitation of the existing Control.Exception API.  This proposal allows us to gracefully update to a new API which does preserve the new info when catching / rethrowing.

* These extra annotations are primarily for debugging purposes.  It shouldn't be a correctness issue for them to be lost due to rethrowing something other than SomeException.

Now one could do something like: catch :: IO a -> (Stack -> IOError -> IO a) -> IO a
but that is not very upgradable and it breaks existing code.

But this is just a matter of requesting information, so one could do something like: catch :: IO a -> (WithStack IOError -> IO a) -> IO a
where: data WithStack e = WithStack Stack e
Or maybe one just addes: catchWithContext :: Exception e => IO a -> (Context -> e -> IO a) -> IO a
Or: catchWithContext :: Exception => IO a -> (Context e -> IO a) -> IO a

Now existing code continues to run and we can feed our exception handlers the data they want, even when we want some specific exception instead of just any exception.

This is a good idea, which is directly supported by this proposal.  You would simply have the implementation of fromException populate the info in your With* datatype.   Or, the definition I would prefer:


    data WithExceptionInfo e = WithExceptionInfo e [SomeExceptionInfo]
        deriving Typeable

    instance Exception e => Exception (WithExceptionInfo e) where
        fromException (SomeExceptionWithInfo e infos) =
            fmap (\e' -> WithExceptionInfo e' infos) (cast e)
        toException (WithExceptionInfo e infos) =
            SomeExceptionWithInfo e infos

Does this help clarify my proposal?  As far as I can tell there is no contradiction or difference between our proposals.  I think you would end up with essentially the same thing I have (maybe with different names ;) ), if you tried implementing your ideas in the context of Control.Exception.