Proposal: Add exception info

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
40 messages Options
12
Reply | Threaded
Open this post in threaded view
|

Proposal: Add exception info

Michael Sloan
Control.Exception currently lacks a good way to supply extra
information along with exceptions.  For example, exceptions could be
thrown along with their callstack[1] or implicit stack[2], but we have
no generic way to include this information with exceptions.

Proposed Solution
=================

The proposed solution is to add a list of `SomeExceptionInfo` to the
`SomeException` datatype.  This list stores additional information
about the exception.  These `ExceptionInfo` instances use a mechanism
which is pretty much identical to the dynamic way the `Exception` type
works:

    data SomeException = forall e . Exception e =>
        SomeExceptionWithInfo e [SomeExceptionInfo]

    data SomeExceptionInfo = forall a . ExceptionInfo a =>
        SomeExceptionInfo a

    class Typeable a => ExceptionInfo a where
        displayExceptionInfo :: a -> String

    addExceptionInfo
        :: (ExceptionInfo a, Exception e)
        => a -> e -> SomeException
    addExceptionInfo x (toException -> SomeExceptionWithInfo e xs) =
        SomeExceptionWithInfo e (SomeExceptionInfo x : xs)

`ExceptionInfo` lacks the to / from functions that `Exception` has,
because I don't see much point in supporting a hierarchy for exception
info.  The `Typeable` superclass constraint supplies the necessary
casting.

`SomeExceptionInfo` could validly instead just use the constraint
`(Typeable a, Show a)`.  However, I believe it's good to have a new
class for this so that:

  * The user can specify a custom `displayExceptionInfo`
  implementation, for when this extra info is presented to the user.
  This function would be invoked by the `show` implementation for
  `SomeException`.

  * Types need to opt-in to be usable with `SomeExceptionInfo`.
  Similarly to exceptions, I imagine that a type with a
  `ExceptionInfo` instance won't be used for anything but acting as
  such an annotation.  Having a class for this allows you to ask GHCI
  about all in-scope exception info types via `:info ExceptionInfo`.

Backwards Compatibility
=======================

GHC 7.10 adds support for bidirectional pattern synonyms.  This means
that this change could be made without breaking code:

    pattern SomeException x <- SomeExceptionWithInfo x _ where
        SomeException x = SomeExceptionWithInfo x []

Note that consumers of this do not need to enable `-XPatternSynonyms`.

Applications
============

Callstacks
----------

As mentioned at the beginning, this can be used to add callstacks to
exceptions:

    newtype ExceptionCallStack =
        ExceptionCallStack { unExceptionCallStack :: [String] }
        deriving Typeable

    instance ExceptionInfo ExceptionCallStack where
        displayExceptionInfo = unlines . unExceptionCallStack

    throwIOWithStack :: Exception e => e -> IO a
    throwIOWithStack e = do
        stack <- currentCallStack
        if null stack
            then throwIO e
            else throwIO (addExceptionInfo (ExceptionCallStack stack) e)

I see little downside for making something like this the default
implementation `throwIO`.  Each rethrowing of the `SomeException`
would add an additional stacktrace to its annotation, much like the
output of `+RTS -xc`.  Unlike this debug output, though, the
stacktraces would be associated with the exception, rather than just
listing locations that exceptions were thrown.  This makes it
tractable to debug exceptions that occur in concurrent programs, or in
programs which frequently throw exceptions during normal functioning.

Throwing Exceptions in Handlers
-------------------------------

Example:

    main =
        throwIO InformativeErrorMessage `finally`
        throwIO ObscureCleanupIssue

While `InformativeErrorMessage` got thrown, the user doesn't see it,
since `ObscureCleanupIssue` is thrown instead.  This causes a few
issues:

1. If the exception is handled by the default handler and yielded to
   the user, then the more informative error is lost.

2. Callers who expect to catch the "Informative error message" won't
   run their handlers for this exception type.

Problem 1 can now easily be resolved by adding some info to the
exception:

    data ExceptionCause = ExceptionCause
        { unExceptionCause :: SomeException }
        deriving Typeable

    instance ExceptionInfo ExceptionCause where
        displayExceptionInfo fe =
            "thrown while handling " ++
            displayException (unExceptionCause fe)

    catch :: Exception e => IO a -> (e -> IO a) -> IO a
    catch f g = f `oldCatch` handler
      where
        handler ex = g ex `oldCatch` \(ex' :: SomeException) ->
            throwIO (addExceptionInfo info ex')
          where
            info = ExceptionCause (toException ex)

This implementation of `catch` is written in a backwards-compatible
way, such that the exception thrown during finalization is still the
one that gets rethrown.  The "original" exception is recorded in the
added info.  This is the same approach used by Python 3's
`__context__` attribute[3].  This was brought to my attention in a
post by Mike Meyer[4], in a thread about having bracket not suppress
the original exception[5].

This doesn't directly resolve issue #2, due to this backwards
compatibility.  With the earlier example, a `catch` handler for
`InformativeErrorMessage` won't be invoked, because it isn't the
exception being rethrown.  This can be resolved by having a variant of
catch which instead throws the original exception.  This might be a
good default for finalization handlers like `bracket` and `finally`.

Asynchronous Exceptions
-----------------------

Currently, the only reliable way to catch exceptions, ignoring async
exceptions, is to fork a new thread.  This is the approach used by the
enclosed-exceptions[6] package.  I think it's quite ugly that we need
to go to such lengths due to the lack of one bit of information about
the exception!  This would resolve ghc trac #5902[7].

base-4.7 added the `SomeAsyncException` type, but this doesn't enforce
anything.  Any exception can be thrown as a sync or async exception.
Instead, we ought to have a reliable way to know if an exception is
synchronous or asynchronous.  Here's what this would look like:

    data IsAsync = IsAsync
        deriving (Typeable, Show)

    instance ExceptionInfo IsAsync where
        displayExceptionInfo IsAsync = "thrown asynchronously"

    throwTo :: Exception e => ThreadId -> e -> IO ()
    throwTo tid = oldThrowTo tid . addExceptionInfo IsAsync

The details of this get a bit tricky: What happens if `throwIO` is
used to rethrow a `SomeException` which has this `IsAsync` flag set?
I'm going to leave out my thoughts on this for now as the interactions
between unsafePerformIO and the concept of "rethrowing" async
exceptions.  Such details are explained in a post by Edsko de Vries[8]
and ghc trac #2558[9].

Issue: fromException loses info
===============================

I can think of one main non-ideal aspect of this proposal:

Currently, the `toException` and `fromException` methods usually form
a prism.  In other words, when `fromException` yields a `Just`, you
should get the same `SomeException` when using `toException` on that
value.

For example,

    fail "testing 1 2 3" `catch` \(ex :: SomeException) -> throwIO ex

is equivalent to

    fail "testing 3 4 5" `catch` \(ex :: IOError) -> throwIO ex

However, with exception info added to just `SomeException`, and no
changes to existing `Exception` instances, this
doesn't hold.  Exceptions caught as a specific exception type get
rethrown with less information.

One resolution to this is be to add `[SomeExceptionInfo]` as a field
to existing `Exception` instances.  This would require the use of
non-default implementations of the `toException` and `fromException`
instances.

Another approach is to have variants of `catch` and `throw` which also
pass around the `[SomeExceptionInfo]`.

[1] https://hackage.haskell.org/package/base-4.8.0.0/docs/GHC-Stack.html#currentCallStack
[2] https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack/ImplicitLocations
[3] https://www.python.org/dev/peps/pep-3134/
[4] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114987.html
[5] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114986.html
[6] https://hackage.haskell.org/package/enclosed-exceptions
[7] https://ghc.haskell.org/trac/ghc/ticket/5902
[8] http://www.edsko.net/2013/06/11/throwTo/
[9] https://ghc.haskell.org/trac/ghc/ticket/2558

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Carter Schonwald
Hey Michael,
I actually proposed something along these lines that got OK'd by libraries early this past fall, the main challenge we hit was actually doing the enginering to add the stack traces to exceptions! theres some nasty module cycles in base that happen when you try to weave things around so that the standard error "message here" call includes some stack trace info. Have you tried to do that simple starter patch to base? 

Chris Allen and I spent like 2 days trying to get it to work and just gave up because of the cycles. We (and others) would probably love some headway on that front.

Theres also some in progress work to use the dwarf debugging info data in >7.10 to provide useful stack traces in the default builds for GHC afaik, 'cause the stack trace functionality you're pointing at currenlty only work on profiled builds

cheers
-Carter

On Tue, Apr 14, 2015 at 2:38 PM, Michael Sloan <[hidden email]> wrote:
Control.Exception currently lacks a good way to supply extra
information along with exceptions.  For example, exceptions could be
thrown along with their callstack[1] or implicit stack[2], but we have
no generic way to include this information with exceptions.

Proposed Solution
=================

The proposed solution is to add a list of `SomeExceptionInfo` to the
`SomeException` datatype.  This list stores additional information
about the exception.  These `ExceptionInfo` instances use a mechanism
which is pretty much identical to the dynamic way the `Exception` type
works:

    data SomeException = forall e . Exception e =>
        SomeExceptionWithInfo e [SomeExceptionInfo]

    data SomeExceptionInfo = forall a . ExceptionInfo a =>
        SomeExceptionInfo a

    class Typeable a => ExceptionInfo a where
        displayExceptionInfo :: a -> String

    addExceptionInfo
        :: (ExceptionInfo a, Exception e)
        => a -> e -> SomeException
    addExceptionInfo x (toException -> SomeExceptionWithInfo e xs) =
        SomeExceptionWithInfo e (SomeExceptionInfo x : xs)

`ExceptionInfo` lacks the to / from functions that `Exception` has,
because I don't see much point in supporting a hierarchy for exception
info.  The `Typeable` superclass constraint supplies the necessary
casting.

`SomeExceptionInfo` could validly instead just use the constraint
`(Typeable a, Show a)`.  However, I believe it's good to have a new
class for this so that:

  * The user can specify a custom `displayExceptionInfo`
  implementation, for when this extra info is presented to the user.
  This function would be invoked by the `show` implementation for
  `SomeException`.

  * Types need to opt-in to be usable with `SomeExceptionInfo`.
  Similarly to exceptions, I imagine that a type with a
  `ExceptionInfo` instance won't be used for anything but acting as
  such an annotation.  Having a class for this allows you to ask GHCI
  about all in-scope exception info types via `:info ExceptionInfo`.

Backwards Compatibility
=======================

GHC 7.10 adds support for bidirectional pattern synonyms.  This means
that this change could be made without breaking code:

    pattern SomeException x <- SomeExceptionWithInfo x _ where
        SomeException x = SomeExceptionWithInfo x []

Note that consumers of this do not need to enable `-XPatternSynonyms`.

Applications
============

Callstacks
----------

As mentioned at the beginning, this can be used to add callstacks to
exceptions:

    newtype ExceptionCallStack =
        ExceptionCallStack { unExceptionCallStack :: [String] }
        deriving Typeable

    instance ExceptionInfo ExceptionCallStack where
        displayExceptionInfo = unlines . unExceptionCallStack

    throwIOWithStack :: Exception e => e -> IO a
    throwIOWithStack e = do
        stack <- currentCallStack
        if null stack
            then throwIO e
            else throwIO (addExceptionInfo (ExceptionCallStack stack) e)

I see little downside for making something like this the default
implementation `throwIO`.  Each rethrowing of the `SomeException`
would add an additional stacktrace to its annotation, much like the
output of `+RTS -xc`.  Unlike this debug output, though, the
stacktraces would be associated with the exception, rather than just
listing locations that exceptions were thrown.  This makes it
tractable to debug exceptions that occur in concurrent programs, or in
programs which frequently throw exceptions during normal functioning.

Throwing Exceptions in Handlers
-------------------------------

Example:

    main =
        throwIO InformativeErrorMessage `finally`
        throwIO ObscureCleanupIssue

While `InformativeErrorMessage` got thrown, the user doesn't see it,
since `ObscureCleanupIssue` is thrown instead.  This causes a few
issues:

1. If the exception is handled by the default handler and yielded to
   the user, then the more informative error is lost.

2. Callers who expect to catch the "Informative error message" won't
   run their handlers for this exception type.

Problem 1 can now easily be resolved by adding some info to the
exception:

    data ExceptionCause = ExceptionCause
        { unExceptionCause :: SomeException }
        deriving Typeable

    instance ExceptionInfo ExceptionCause where
        displayExceptionInfo fe =
            "thrown while handling " ++
            displayException (unExceptionCause fe)

    catch :: Exception e => IO a -> (e -> IO a) -> IO a
    catch f g = f `oldCatch` handler
      where
        handler ex = g ex `oldCatch` \(ex' :: SomeException) ->
            throwIO (addExceptionInfo info ex')
          where
            info = ExceptionCause (toException ex)

This implementation of `catch` is written in a backwards-compatible
way, such that the exception thrown during finalization is still the
one that gets rethrown.  The "original" exception is recorded in the
added info.  This is the same approach used by Python 3's
`__context__` attribute[3].  This was brought to my attention in a
post by Mike Meyer[4], in a thread about having bracket not suppress
the original exception[5].

This doesn't directly resolve issue #2, due to this backwards
compatibility.  With the earlier example, a `catch` handler for
`InformativeErrorMessage` won't be invoked, because it isn't the
exception being rethrown.  This can be resolved by having a variant of
catch which instead throws the original exception.  This might be a
good default for finalization handlers like `bracket` and `finally`.

Asynchronous Exceptions
-----------------------

Currently, the only reliable way to catch exceptions, ignoring async
exceptions, is to fork a new thread.  This is the approach used by the
enclosed-exceptions[6] package.  I think it's quite ugly that we need
to go to such lengths due to the lack of one bit of information about
the exception!  This would resolve ghc trac #5902[7].

base-4.7 added the `SomeAsyncException` type, but this doesn't enforce
anything.  Any exception can be thrown as a sync or async exception.
Instead, we ought to have a reliable way to know if an exception is
synchronous or asynchronous.  Here's what this would look like:

    data IsAsync = IsAsync
        deriving (Typeable, Show)

    instance ExceptionInfo IsAsync where
        displayExceptionInfo IsAsync = "thrown asynchronously"

    throwTo :: Exception e => ThreadId -> e -> IO ()
    throwTo tid = oldThrowTo tid . addExceptionInfo IsAsync

The details of this get a bit tricky: What happens if `throwIO` is
used to rethrow a `SomeException` which has this `IsAsync` flag set?
I'm going to leave out my thoughts on this for now as the interactions
between unsafePerformIO and the concept of "rethrowing" async
exceptions.  Such details are explained in a post by Edsko de Vries[8]
and ghc trac #2558[9].

Issue: fromException loses info
===============================

I can think of one main non-ideal aspect of this proposal:

Currently, the `toException` and `fromException` methods usually form
a prism.  In other words, when `fromException` yields a `Just`, you
should get the same `SomeException` when using `toException` on that
value.

For example,

    fail "testing 1 2 3" `catch` \(ex :: SomeException) -> throwIO ex

is equivalent to

    fail "testing 3 4 5" `catch` \(ex :: IOError) -> throwIO ex

However, with exception info added to just `SomeException`, and no
changes to existing `Exception` instances, this
doesn't hold.  Exceptions caught as a specific exception type get
rethrown with less information.

One resolution to this is be to add `[SomeExceptionInfo]` as a field
to existing `Exception` instances.  This would require the use of
non-default implementations of the `toException` and `fromException`
instances.

Another approach is to have variants of `catch` and `throw` which also
pass around the `[SomeExceptionInfo]`.

[1] https://hackage.haskell.org/package/base-4.8.0.0/docs/GHC-Stack.html#currentCallStack
[2] https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack/ImplicitLocations
[3] https://www.python.org/dev/peps/pep-3134/
[4] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114987.html
[5] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114986.html
[6] https://hackage.haskell.org/package/enclosed-exceptions
[7] https://ghc.haskell.org/trac/ghc/ticket/5902
[8] http://www.edsko.net/2013/06/11/throwTo/
[9] https://ghc.haskell.org/trac/ghc/ticket/2558

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries



_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Michael Sloan
Hi Carter!


I haven't tried this as a patch to base, but I'm certain that the core of the proposal has no extra dependencies.  Note that the proposal isn't about stack traces in particular - that's just one application of being able to throw exceptions with extra information.

Even if `throwTo` isn't modified to throw exceptions with stack traces, this functionality could be provided outside of `Control.Exception` (though, that does seem like the right place to put it).  I'm surprised that the circularity was so problematic, though.  Why isn't it sufficient to have an hs-boot file for `GHC.Stack`, which exports `currentCallStack`?

-Michael

On Tue, Apr 14, 2015 at 7:55 PM, Carter Schonwald <[hidden email]> wrote:
Hey Michael,
I actually proposed something along these lines that got OK'd by libraries early this past fall, the main challenge we hit was actually doing the enginering to add the stack traces to exceptions! theres some nasty module cycles in base that happen when you try to weave things around so that the standard error "message here" call includes some stack trace info. Have you tried to do that simple starter patch to base? 

Chris Allen and I spent like 2 days trying to get it to work and just gave up because of the cycles. We (and others) would probably love some headway on that front.

Theres also some in progress work to use the dwarf debugging info data in >7.10 to provide useful stack traces in the default builds for GHC afaik, 'cause the stack trace functionality you're pointing at currenlty only work on profiled builds

cheers
-Carter

On Tue, Apr 14, 2015 at 2:38 PM, Michael Sloan <[hidden email]> wrote:
Control.Exception currently lacks a good way to supply extra
information along with exceptions.  For example, exceptions could be
thrown along with their callstack[1] or implicit stack[2], but we have
no generic way to include this information with exceptions.

Proposed Solution
=================

The proposed solution is to add a list of `SomeExceptionInfo` to the
`SomeException` datatype.  This list stores additional information
about the exception.  These `ExceptionInfo` instances use a mechanism
which is pretty much identical to the dynamic way the `Exception` type
works:

    data SomeException = forall e . Exception e =>
        SomeExceptionWithInfo e [SomeExceptionInfo]

    data SomeExceptionInfo = forall a . ExceptionInfo a =>
        SomeExceptionInfo a

    class Typeable a => ExceptionInfo a where
        displayExceptionInfo :: a -> String

    addExceptionInfo
        :: (ExceptionInfo a, Exception e)
        => a -> e -> SomeException
    addExceptionInfo x (toException -> SomeExceptionWithInfo e xs) =
        SomeExceptionWithInfo e (SomeExceptionInfo x : xs)

`ExceptionInfo` lacks the to / from functions that `Exception` has,
because I don't see much point in supporting a hierarchy for exception
info.  The `Typeable` superclass constraint supplies the necessary
casting.

`SomeExceptionInfo` could validly instead just use the constraint
`(Typeable a, Show a)`.  However, I believe it's good to have a new
class for this so that:

  * The user can specify a custom `displayExceptionInfo`
  implementation, for when this extra info is presented to the user.
  This function would be invoked by the `show` implementation for
  `SomeException`.

  * Types need to opt-in to be usable with `SomeExceptionInfo`.
  Similarly to exceptions, I imagine that a type with a
  `ExceptionInfo` instance won't be used for anything but acting as
  such an annotation.  Having a class for this allows you to ask GHCI
  about all in-scope exception info types via `:info ExceptionInfo`.

Backwards Compatibility
=======================

GHC 7.10 adds support for bidirectional pattern synonyms.  This means
that this change could be made without breaking code:

    pattern SomeException x <- SomeExceptionWithInfo x _ where
        SomeException x = SomeExceptionWithInfo x []

Note that consumers of this do not need to enable `-XPatternSynonyms`.

Applications
============

Callstacks
----------

As mentioned at the beginning, this can be used to add callstacks to
exceptions:

    newtype ExceptionCallStack =
        ExceptionCallStack { unExceptionCallStack :: [String] }
        deriving Typeable

    instance ExceptionInfo ExceptionCallStack where
        displayExceptionInfo = unlines . unExceptionCallStack

    throwIOWithStack :: Exception e => e -> IO a
    throwIOWithStack e = do
        stack <- currentCallStack
        if null stack
            then throwIO e
            else throwIO (addExceptionInfo (ExceptionCallStack stack) e)

I see little downside for making something like this the default
implementation `throwIO`.  Each rethrowing of the `SomeException`
would add an additional stacktrace to its annotation, much like the
output of `+RTS -xc`.  Unlike this debug output, though, the
stacktraces would be associated with the exception, rather than just
listing locations that exceptions were thrown.  This makes it
tractable to debug exceptions that occur in concurrent programs, or in
programs which frequently throw exceptions during normal functioning.

Throwing Exceptions in Handlers
-------------------------------

Example:

    main =
        throwIO InformativeErrorMessage `finally`
        throwIO ObscureCleanupIssue

While `InformativeErrorMessage` got thrown, the user doesn't see it,
since `ObscureCleanupIssue` is thrown instead.  This causes a few
issues:

1. If the exception is handled by the default handler and yielded to
   the user, then the more informative error is lost.

2. Callers who expect to catch the "Informative error message" won't
   run their handlers for this exception type.

Problem 1 can now easily be resolved by adding some info to the
exception:

    data ExceptionCause = ExceptionCause
        { unExceptionCause :: SomeException }
        deriving Typeable

    instance ExceptionInfo ExceptionCause where
        displayExceptionInfo fe =
            "thrown while handling " ++
            displayException (unExceptionCause fe)

    catch :: Exception e => IO a -> (e -> IO a) -> IO a
    catch f g = f `oldCatch` handler
      where
        handler ex = g ex `oldCatch` \(ex' :: SomeException) ->
            throwIO (addExceptionInfo info ex')
          where
            info = ExceptionCause (toException ex)

This implementation of `catch` is written in a backwards-compatible
way, such that the exception thrown during finalization is still the
one that gets rethrown.  The "original" exception is recorded in the
added info.  This is the same approach used by Python 3's
`__context__` attribute[3].  This was brought to my attention in a
post by Mike Meyer[4], in a thread about having bracket not suppress
the original exception[5].

This doesn't directly resolve issue #2, due to this backwards
compatibility.  With the earlier example, a `catch` handler for
`InformativeErrorMessage` won't be invoked, because it isn't the
exception being rethrown.  This can be resolved by having a variant of
catch which instead throws the original exception.  This might be a
good default for finalization handlers like `bracket` and `finally`.

Asynchronous Exceptions
-----------------------

Currently, the only reliable way to catch exceptions, ignoring async
exceptions, is to fork a new thread.  This is the approach used by the
enclosed-exceptions[6] package.  I think it's quite ugly that we need
to go to such lengths due to the lack of one bit of information about
the exception!  This would resolve ghc trac #5902[7].

base-4.7 added the `SomeAsyncException` type, but this doesn't enforce
anything.  Any exception can be thrown as a sync or async exception.
Instead, we ought to have a reliable way to know if an exception is
synchronous or asynchronous.  Here's what this would look like:

    data IsAsync = IsAsync
        deriving (Typeable, Show)

    instance ExceptionInfo IsAsync where
        displayExceptionInfo IsAsync = "thrown asynchronously"

    throwTo :: Exception e => ThreadId -> e -> IO ()
    throwTo tid = oldThrowTo tid . addExceptionInfo IsAsync

The details of this get a bit tricky: What happens if `throwIO` is
used to rethrow a `SomeException` which has this `IsAsync` flag set?
I'm going to leave out my thoughts on this for now as the interactions
between unsafePerformIO and the concept of "rethrowing" async
exceptions.  Such details are explained in a post by Edsko de Vries[8]
and ghc trac #2558[9].

Issue: fromException loses info
===============================

I can think of one main non-ideal aspect of this proposal:

Currently, the `toException` and `fromException` methods usually form
a prism.  In other words, when `fromException` yields a `Just`, you
should get the same `SomeException` when using `toException` on that
value.

For example,

    fail "testing 1 2 3" `catch` \(ex :: SomeException) -> throwIO ex

is equivalent to

    fail "testing 3 4 5" `catch` \(ex :: IOError) -> throwIO ex

However, with exception info added to just `SomeException`, and no
changes to existing `Exception` instances, this
doesn't hold.  Exceptions caught as a specific exception type get
rethrown with less information.

One resolution to this is be to add `[SomeExceptionInfo]` as a field
to existing `Exception` instances.  This would require the use of
non-default implementations of the `toException` and `fromException`
instances.

Another approach is to have variants of `catch` and `throw` which also
pass around the `[SomeExceptionInfo]`.

[1] https://hackage.haskell.org/package/base-4.8.0.0/docs/GHC-Stack.html#currentCallStack
[2] https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack/ImplicitLocations
[3] https://www.python.org/dev/peps/pep-3134/
[4] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114987.html
[5] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114986.html
[6] https://hackage.haskell.org/package/enclosed-exceptions
[7] https://ghc.haskell.org/trac/ghc/ticket/5902
[8] http://www.edsko.net/2013/06/11/throwTo/
[9] https://ghc.haskell.org/trac/ghc/ticket/2558

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries




_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Carter Schonwald
if you can patch prelude error to include stack traces, i will owe you a >=1 beer each at the next two icfps. Thats all i want for christmas. :)

i can't speak for how a different patch might work out, because thats not what I'd tried at the time. If you have a go, please share the results!
-Carter

On Wed, Apr 15, 2015 at 12:22 AM, Michael Sloan <[hidden email]> wrote:
Hi Carter!


I haven't tried this as a patch to base, but I'm certain that the core of the proposal has no extra dependencies.  Note that the proposal isn't about stack traces in particular - that's just one application of being able to throw exceptions with extra information.

Even if `throwTo` isn't modified to throw exceptions with stack traces, this functionality could be provided outside of `Control.Exception` (though, that does seem like the right place to put it).  I'm surprised that the circularity was so problematic, though.  Why isn't it sufficient to have an hs-boot file for `GHC.Stack`, which exports `currentCallStack`?

-Michael

On Tue, Apr 14, 2015 at 7:55 PM, Carter Schonwald <[hidden email]> wrote:
Hey Michael,
I actually proposed something along these lines that got OK'd by libraries early this past fall, the main challenge we hit was actually doing the enginering to add the stack traces to exceptions! theres some nasty module cycles in base that happen when you try to weave things around so that the standard error "message here" call includes some stack trace info. Have you tried to do that simple starter patch to base? 

Chris Allen and I spent like 2 days trying to get it to work and just gave up because of the cycles. We (and others) would probably love some headway on that front.

Theres also some in progress work to use the dwarf debugging info data in >7.10 to provide useful stack traces in the default builds for GHC afaik, 'cause the stack trace functionality you're pointing at currenlty only work on profiled builds

cheers
-Carter

On Tue, Apr 14, 2015 at 2:38 PM, Michael Sloan <[hidden email]> wrote:
Control.Exception currently lacks a good way to supply extra
information along with exceptions.  For example, exceptions could be
thrown along with their callstack[1] or implicit stack[2], but we have
no generic way to include this information with exceptions.

Proposed Solution
=================

The proposed solution is to add a list of `SomeExceptionInfo` to the
`SomeException` datatype.  This list stores additional information
about the exception.  These `ExceptionInfo` instances use a mechanism
which is pretty much identical to the dynamic way the `Exception` type
works:

    data SomeException = forall e . Exception e =>
        SomeExceptionWithInfo e [SomeExceptionInfo]

    data SomeExceptionInfo = forall a . ExceptionInfo a =>
        SomeExceptionInfo a

    class Typeable a => ExceptionInfo a where
        displayExceptionInfo :: a -> String

    addExceptionInfo
        :: (ExceptionInfo a, Exception e)
        => a -> e -> SomeException
    addExceptionInfo x (toException -> SomeExceptionWithInfo e xs) =
        SomeExceptionWithInfo e (SomeExceptionInfo x : xs)

`ExceptionInfo` lacks the to / from functions that `Exception` has,
because I don't see much point in supporting a hierarchy for exception
info.  The `Typeable` superclass constraint supplies the necessary
casting.

`SomeExceptionInfo` could validly instead just use the constraint
`(Typeable a, Show a)`.  However, I believe it's good to have a new
class for this so that:

  * The user can specify a custom `displayExceptionInfo`
  implementation, for when this extra info is presented to the user.
  This function would be invoked by the `show` implementation for
  `SomeException`.

  * Types need to opt-in to be usable with `SomeExceptionInfo`.
  Similarly to exceptions, I imagine that a type with a
  `ExceptionInfo` instance won't be used for anything but acting as
  such an annotation.  Having a class for this allows you to ask GHCI
  about all in-scope exception info types via `:info ExceptionInfo`.

Backwards Compatibility
=======================

GHC 7.10 adds support for bidirectional pattern synonyms.  This means
that this change could be made without breaking code:

    pattern SomeException x <- SomeExceptionWithInfo x _ where
        SomeException x = SomeExceptionWithInfo x []

Note that consumers of this do not need to enable `-XPatternSynonyms`.

Applications
============

Callstacks
----------

As mentioned at the beginning, this can be used to add callstacks to
exceptions:

    newtype ExceptionCallStack =
        ExceptionCallStack { unExceptionCallStack :: [String] }
        deriving Typeable

    instance ExceptionInfo ExceptionCallStack where
        displayExceptionInfo = unlines . unExceptionCallStack

    throwIOWithStack :: Exception e => e -> IO a
    throwIOWithStack e = do
        stack <- currentCallStack
        if null stack
            then throwIO e
            else throwIO (addExceptionInfo (ExceptionCallStack stack) e)

I see little downside for making something like this the default
implementation `throwIO`.  Each rethrowing of the `SomeException`
would add an additional stacktrace to its annotation, much like the
output of `+RTS -xc`.  Unlike this debug output, though, the
stacktraces would be associated with the exception, rather than just
listing locations that exceptions were thrown.  This makes it
tractable to debug exceptions that occur in concurrent programs, or in
programs which frequently throw exceptions during normal functioning.

Throwing Exceptions in Handlers
-------------------------------

Example:

    main =
        throwIO InformativeErrorMessage `finally`
        throwIO ObscureCleanupIssue

While `InformativeErrorMessage` got thrown, the user doesn't see it,
since `ObscureCleanupIssue` is thrown instead.  This causes a few
issues:

1. If the exception is handled by the default handler and yielded to
   the user, then the more informative error is lost.

2. Callers who expect to catch the "Informative error message" won't
   run their handlers for this exception type.

Problem 1 can now easily be resolved by adding some info to the
exception:

    data ExceptionCause = ExceptionCause
        { unExceptionCause :: SomeException }
        deriving Typeable

    instance ExceptionInfo ExceptionCause where
        displayExceptionInfo fe =
            "thrown while handling " ++
            displayException (unExceptionCause fe)

    catch :: Exception e => IO a -> (e -> IO a) -> IO a
    catch f g = f `oldCatch` handler
      where
        handler ex = g ex `oldCatch` \(ex' :: SomeException) ->
            throwIO (addExceptionInfo info ex')
          where
            info = ExceptionCause (toException ex)

This implementation of `catch` is written in a backwards-compatible
way, such that the exception thrown during finalization is still the
one that gets rethrown.  The "original" exception is recorded in the
added info.  This is the same approach used by Python 3's
`__context__` attribute[3].  This was brought to my attention in a
post by Mike Meyer[4], in a thread about having bracket not suppress
the original exception[5].

This doesn't directly resolve issue #2, due to this backwards
compatibility.  With the earlier example, a `catch` handler for
`InformativeErrorMessage` won't be invoked, because it isn't the
exception being rethrown.  This can be resolved by having a variant of
catch which instead throws the original exception.  This might be a
good default for finalization handlers like `bracket` and `finally`.

Asynchronous Exceptions
-----------------------

Currently, the only reliable way to catch exceptions, ignoring async
exceptions, is to fork a new thread.  This is the approach used by the
enclosed-exceptions[6] package.  I think it's quite ugly that we need
to go to such lengths due to the lack of one bit of information about
the exception!  This would resolve ghc trac #5902[7].

base-4.7 added the `SomeAsyncException` type, but this doesn't enforce
anything.  Any exception can be thrown as a sync or async exception.
Instead, we ought to have a reliable way to know if an exception is
synchronous or asynchronous.  Here's what this would look like:

    data IsAsync = IsAsync
        deriving (Typeable, Show)

    instance ExceptionInfo IsAsync where
        displayExceptionInfo IsAsync = "thrown asynchronously"

    throwTo :: Exception e => ThreadId -> e -> IO ()
    throwTo tid = oldThrowTo tid . addExceptionInfo IsAsync

The details of this get a bit tricky: What happens if `throwIO` is
used to rethrow a `SomeException` which has this `IsAsync` flag set?
I'm going to leave out my thoughts on this for now as the interactions
between unsafePerformIO and the concept of "rethrowing" async
exceptions.  Such details are explained in a post by Edsko de Vries[8]
and ghc trac #2558[9].

Issue: fromException loses info
===============================

I can think of one main non-ideal aspect of this proposal:

Currently, the `toException` and `fromException` methods usually form
a prism.  In other words, when `fromException` yields a `Just`, you
should get the same `SomeException` when using `toException` on that
value.

For example,

    fail "testing 1 2 3" `catch` \(ex :: SomeException) -> throwIO ex

is equivalent to

    fail "testing 3 4 5" `catch` \(ex :: IOError) -> throwIO ex

However, with exception info added to just `SomeException`, and no
changes to existing `Exception` instances, this
doesn't hold.  Exceptions caught as a specific exception type get
rethrown with less information.

One resolution to this is be to add `[SomeExceptionInfo]` as a field
to existing `Exception` instances.  This would require the use of
non-default implementations of the `toException` and `fromException`
instances.

Another approach is to have variants of `catch` and `throw` which also
pass around the `[SomeExceptionInfo]`.

[1] https://hackage.haskell.org/package/base-4.8.0.0/docs/GHC-Stack.html#currentCallStack
[2] https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack/ImplicitLocations
[3] https://www.python.org/dev/peps/pep-3134/
[4] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114987.html
[5] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114986.html
[6] https://hackage.haskell.org/package/enclosed-exceptions
[7] https://ghc.haskell.org/trac/ghc/ticket/5902
[8] http://www.edsko.net/2013/06/11/throwTo/
[9] https://ghc.haskell.org/trac/ghc/ticket/2558

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries





_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Michael Snoyman
In reply to this post by Carter Schonwald
Sorry to confuse this thread with a second Michael.

I spoke with Michael Sloan about this proposal before he made it. I'm usually very hesitant to introduce breaking changes to core APIs, but in this case (1) there's a very good use case that's currently excluded by the API, and (2) Michael Sloan figured out some great ways to minimize the breakage. You could even argue that this proposal has *no* API breakage.

I'm +1 on including it. I'm also hopeful that we can address the `error = errorWithStackTrace`, but that would really be a step 2 after the changes to SomeException are made.

On Wed, Apr 15, 2015 at 5:56 AM Carter Schonwald <[hidden email]> wrote:
Hey Michael,
I actually proposed something along these lines that got OK'd by libraries early this past fall, the main challenge we hit was actually doing the enginering to add the stack traces to exceptions! theres some nasty module cycles in base that happen when you try to weave things around so that the standard error "message here" call includes some stack trace info. Have you tried to do that simple starter patch to base? 

Chris Allen and I spent like 2 days trying to get it to work and just gave up because of the cycles. We (and others) would probably love some headway on that front.

Theres also some in progress work to use the dwarf debugging info data in >7.10 to provide useful stack traces in the default builds for GHC afaik, 'cause the stack trace functionality you're pointing at currenlty only work on profiled builds

cheers
-Carter

On Tue, Apr 14, 2015 at 2:38 PM, Michael Sloan <[hidden email]> wrote:
Control.Exception currently lacks a good way to supply extra
information along with exceptions.  For example, exceptions could be
thrown along with their callstack[1] or implicit stack[2], but we have
no generic way to include this information with exceptions.

Proposed Solution
=================

The proposed solution is to add a list of `SomeExceptionInfo` to the
`SomeException` datatype.  This list stores additional information
about the exception.  These `ExceptionInfo` instances use a mechanism
which is pretty much identical to the dynamic way the `Exception` type
works:

    data SomeException = forall e . Exception e =>
        SomeExceptionWithInfo e [SomeExceptionInfo]

    data SomeExceptionInfo = forall a . ExceptionInfo a =>
        SomeExceptionInfo a

    class Typeable a => ExceptionInfo a where
        displayExceptionInfo :: a -> String

    addExceptionInfo
        :: (ExceptionInfo a, Exception e)
        => a -> e -> SomeException
    addExceptionInfo x (toException -> SomeExceptionWithInfo e xs) =
        SomeExceptionWithInfo e (SomeExceptionInfo x : xs)

`ExceptionInfo` lacks the to / from functions that `Exception` has,
because I don't see much point in supporting a hierarchy for exception
info.  The `Typeable` superclass constraint supplies the necessary
casting.

`SomeExceptionInfo` could validly instead just use the constraint
`(Typeable a, Show a)`.  However, I believe it's good to have a new
class for this so that:

  * The user can specify a custom `displayExceptionInfo`
  implementation, for when this extra info is presented to the user.
  This function would be invoked by the `show` implementation for
  `SomeException`.

  * Types need to opt-in to be usable with `SomeExceptionInfo`.
  Similarly to exceptions, I imagine that a type with a
  `ExceptionInfo` instance won't be used for anything but acting as
  such an annotation.  Having a class for this allows you to ask GHCI
  about all in-scope exception info types via `:info ExceptionInfo`.

Backwards Compatibility
=======================

GHC 7.10 adds support for bidirectional pattern synonyms.  This means
that this change could be made without breaking code:

    pattern SomeException x <- SomeExceptionWithInfo x _ where
        SomeException x = SomeExceptionWithInfo x []

Note that consumers of this do not need to enable `-XPatternSynonyms`.

Applications
============

Callstacks
----------

As mentioned at the beginning, this can be used to add callstacks to
exceptions:

    newtype ExceptionCallStack =
        ExceptionCallStack { unExceptionCallStack :: [String] }
        deriving Typeable

    instance ExceptionInfo ExceptionCallStack where
        displayExceptionInfo = unlines . unExceptionCallStack

    throwIOWithStack :: Exception e => e -> IO a
    throwIOWithStack e = do
        stack <- currentCallStack
        if null stack
            then throwIO e
            else throwIO (addExceptionInfo (ExceptionCallStack stack) e)

I see little downside for making something like this the default
implementation `throwIO`.  Each rethrowing of the `SomeException`
would add an additional stacktrace to its annotation, much like the
output of `+RTS -xc`.  Unlike this debug output, though, the
stacktraces would be associated with the exception, rather than just
listing locations that exceptions were thrown.  This makes it
tractable to debug exceptions that occur in concurrent programs, or in
programs which frequently throw exceptions during normal functioning.

Throwing Exceptions in Handlers
-------------------------------

Example:

    main =
        throwIO InformativeErrorMessage `finally`
        throwIO ObscureCleanupIssue

While `InformativeErrorMessage` got thrown, the user doesn't see it,
since `ObscureCleanupIssue` is thrown instead.  This causes a few
issues:

1. If the exception is handled by the default handler and yielded to
   the user, then the more informative error is lost.

2. Callers who expect to catch the "Informative error message" won't
   run their handlers for this exception type.

Problem 1 can now easily be resolved by adding some info to the
exception:

    data ExceptionCause = ExceptionCause
        { unExceptionCause :: SomeException }
        deriving Typeable

    instance ExceptionInfo ExceptionCause where
        displayExceptionInfo fe =
            "thrown while handling " ++
            displayException (unExceptionCause fe)

    catch :: Exception e => IO a -> (e -> IO a) -> IO a
    catch f g = f `oldCatch` handler
      where
        handler ex = g ex `oldCatch` \(ex' :: SomeException) ->
            throwIO (addExceptionInfo info ex')
          where
            info = ExceptionCause (toException ex)

This implementation of `catch` is written in a backwards-compatible
way, such that the exception thrown during finalization is still the
one that gets rethrown.  The "original" exception is recorded in the
added info.  This is the same approach used by Python 3's
`__context__` attribute[3].  This was brought to my attention in a
post by Mike Meyer[4], in a thread about having bracket not suppress
the original exception[5].

This doesn't directly resolve issue #2, due to this backwards
compatibility.  With the earlier example, a `catch` handler for
`InformativeErrorMessage` won't be invoked, because it isn't the
exception being rethrown.  This can be resolved by having a variant of
catch which instead throws the original exception.  This might be a
good default for finalization handlers like `bracket` and `finally`.

Asynchronous Exceptions
-----------------------

Currently, the only reliable way to catch exceptions, ignoring async
exceptions, is to fork a new thread.  This is the approach used by the
enclosed-exceptions[6] package.  I think it's quite ugly that we need
to go to such lengths due to the lack of one bit of information about
the exception!  This would resolve ghc trac #5902[7].

base-4.7 added the `SomeAsyncException` type, but this doesn't enforce
anything.  Any exception can be thrown as a sync or async exception.
Instead, we ought to have a reliable way to know if an exception is
synchronous or asynchronous.  Here's what this would look like:

    data IsAsync = IsAsync
        deriving (Typeable, Show)

    instance ExceptionInfo IsAsync where
        displayExceptionInfo IsAsync = "thrown asynchronously"

    throwTo :: Exception e => ThreadId -> e -> IO ()
    throwTo tid = oldThrowTo tid . addExceptionInfo IsAsync

The details of this get a bit tricky: What happens if `throwIO` is
used to rethrow a `SomeException` which has this `IsAsync` flag set?
I'm going to leave out my thoughts on this for now as the interactions
between unsafePerformIO and the concept of "rethrowing" async
exceptions.  Such details are explained in a post by Edsko de Vries[8]
and ghc trac #2558[9].

Issue: fromException loses info
===============================

I can think of one main non-ideal aspect of this proposal:

Currently, the `toException` and `fromException` methods usually form
a prism.  In other words, when `fromException` yields a `Just`, you
should get the same `SomeException` when using `toException` on that
value.

For example,

    fail "testing 1 2 3" `catch` \(ex :: SomeException) -> throwIO ex

is equivalent to

    fail "testing 3 4 5" `catch` \(ex :: IOError) -> throwIO ex

However, with exception info added to just `SomeException`, and no
changes to existing `Exception` instances, this
doesn't hold.  Exceptions caught as a specific exception type get
rethrown with less information.

One resolution to this is be to add `[SomeExceptionInfo]` as a field
to existing `Exception` instances.  This would require the use of
non-default implementations of the `toException` and `fromException`
instances.

Another approach is to have variants of `catch` and `throw` which also
pass around the `[SomeExceptionInfo]`.

[1] https://hackage.haskell.org/package/base-4.8.0.0/docs/GHC-Stack.html#currentCallStack
[2] https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack/ImplicitLocations
[3] https://www.python.org/dev/peps/pep-3134/
[4] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114987.html
[5] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114986.html
[6] https://hackage.haskell.org/package/enclosed-exceptions
[7] https://ghc.haskell.org/trac/ghc/ticket/5902
[8] http://www.edsko.net/2013/06/11/throwTo/
[9] https://ghc.haskell.org/trac/ghc/ticket/2558

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries


_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

John Lato-2
In reply to this post by Carter Schonwald

I don't have a strong opinion towards this proposal.  It definitely seems useful, but I think most of the uses would be either somewhat niche in practice, or better addressed by other mechanisms (the call stack stuff).

However I am opposed to adding IsAsync, because I do not think it is useful. In every case I know where a user wants to differentiate async exceptions, what they actually want is to differentiate RTS control exceptions from everything else.  I think the proposal would easily accommodate that, and you could have IsAsync too if there is a use case for it.


On Thu, Apr 16, 2015, 8:08 PM Carter Schonwald <[hidden email]> wrote:
if you can patch prelude error to include stack traces, i will owe you a >=1 beer each at the next two icfps. Thats all i want for christmas. :)

i can't speak for how a different patch might work out, because thats not what I'd tried at the time. If you have a go, please share the results!
-Carter

On Wed, Apr 15, 2015 at 12:22 AM, Michael Sloan <[hidden email]> wrote:
Hi Carter!


I haven't tried this as a patch to base, but I'm certain that the core of the proposal has no extra dependencies.  Note that the proposal isn't about stack traces in particular - that's just one application of being able to throw exceptions with extra information.

Even if `throwTo` isn't modified to throw exceptions with stack traces, this functionality could be provided outside of `Control.Exception` (though, that does seem like the right place to put it).  I'm surprised that the circularity was so problematic, though.  Why isn't it sufficient to have an hs-boot file for `GHC.Stack`, which exports `currentCallStack`?

-Michael

On Tue, Apr 14, 2015 at 7:55 PM, Carter Schonwald <[hidden email]> wrote:
Hey Michael,
I actually proposed something along these lines that got OK'd by libraries early this past fall, the main challenge we hit was actually doing the enginering to add the stack traces to exceptions! theres some nasty module cycles in base that happen when you try to weave things around so that the standard error "message here" call includes some stack trace info. Have you tried to do that simple starter patch to base? 

Chris Allen and I spent like 2 days trying to get it to work and just gave up because of the cycles. We (and others) would probably love some headway on that front.

Theres also some in progress work to use the dwarf debugging info data in >7.10 to provide useful stack traces in the default builds for GHC afaik, 'cause the stack trace functionality you're pointing at currenlty only work on profiled builds

cheers
-Carter

On Tue, Apr 14, 2015 at 2:38 PM, Michael Sloan <[hidden email]> wrote:
Control.Exception currently lacks a good way to supply extra
information along with exceptions.  For example, exceptions could be
thrown along with their callstack[1] or implicit stack[2], but we have
no generic way to include this information with exceptions.

Proposed Solution
=================

The proposed solution is to add a list of `SomeExceptionInfo` to the
`SomeException` datatype.  This list stores additional information
about the exception.  These `ExceptionInfo` instances use a mechanism
which is pretty much identical to the dynamic way the `Exception` type
works:

    data SomeException = forall e . Exception e =>
        SomeExceptionWithInfo e [SomeExceptionInfo]

    data SomeExceptionInfo = forall a . ExceptionInfo a =>
        SomeExceptionInfo a

    class Typeable a => ExceptionInfo a where
        displayExceptionInfo :: a -> String

    addExceptionInfo
        :: (ExceptionInfo a, Exception e)
        => a -> e -> SomeException
    addExceptionInfo x (toException -> SomeExceptionWithInfo e xs) =
        SomeExceptionWithInfo e (SomeExceptionInfo x : xs)

`ExceptionInfo` lacks the to / from functions that `Exception` has,
because I don't see much point in supporting a hierarchy for exception
info.  The `Typeable` superclass constraint supplies the necessary
casting.

`SomeExceptionInfo` could validly instead just use the constraint
`(Typeable a, Show a)`.  However, I believe it's good to have a new
class for this so that:

  * The user can specify a custom `displayExceptionInfo`
  implementation, for when this extra info is presented to the user.
  This function would be invoked by the `show` implementation for
  `SomeException`.

  * Types need to opt-in to be usable with `SomeExceptionInfo`.
  Similarly to exceptions, I imagine that a type with a
  `ExceptionInfo` instance won't be used for anything but acting as
  such an annotation.  Having a class for this allows you to ask GHCI
  about all in-scope exception info types via `:info ExceptionInfo`.

Backwards Compatibility
=======================

GHC 7.10 adds support for bidirectional pattern synonyms.  This means
that this change could be made without breaking code:

    pattern SomeException x <- SomeExceptionWithInfo x _ where
        SomeException x = SomeExceptionWithInfo x []

Note that consumers of this do not need to enable `-XPatternSynonyms`.

Applications
============

Callstacks
----------

As mentioned at the beginning, this can be used to add callstacks to
exceptions:

    newtype ExceptionCallStack =
        ExceptionCallStack { unExceptionCallStack :: [String] }
        deriving Typeable

    instance ExceptionInfo ExceptionCallStack where
        displayExceptionInfo = unlines . unExceptionCallStack

    throwIOWithStack :: Exception e => e -> IO a
    throwIOWithStack e = do
        stack <- currentCallStack
        if null stack
            then throwIO e
            else throwIO (addExceptionInfo (ExceptionCallStack stack) e)

I see little downside for making something like this the default
implementation `throwIO`.  Each rethrowing of the `SomeException`
would add an additional stacktrace to its annotation, much like the
output of `+RTS -xc`.  Unlike this debug output, though, the
stacktraces would be associated with the exception, rather than just
listing locations that exceptions were thrown.  This makes it
tractable to debug exceptions that occur in concurrent programs, or in
programs which frequently throw exceptions during normal functioning.

Throwing Exceptions in Handlers
-------------------------------

Example:

    main =
        throwIO InformativeErrorMessage `finally`
        throwIO ObscureCleanupIssue

While `InformativeErrorMessage` got thrown, the user doesn't see it,
since `ObscureCleanupIssue` is thrown instead.  This causes a few
issues:

1. If the exception is handled by the default handler and yielded to
   the user, then the more informative error is lost.

2. Callers who expect to catch the "Informative error message" won't
   run their handlers for this exception type.

Problem 1 can now easily be resolved by adding some info to the
exception:

    data ExceptionCause = ExceptionCause
        { unExceptionCause :: SomeException }
        deriving Typeable

    instance ExceptionInfo ExceptionCause where
        displayExceptionInfo fe =
            "thrown while handling " ++
            displayException (unExceptionCause fe)

    catch :: Exception e => IO a -> (e -> IO a) -> IO a
    catch f g = f `oldCatch` handler
      where
        handler ex = g ex `oldCatch` \(ex' :: SomeException) ->
            throwIO (addExceptionInfo info ex')
          where
            info = ExceptionCause (toException ex)

This implementation of `catch` is written in a backwards-compatible
way, such that the exception thrown during finalization is still the
one that gets rethrown.  The "original" exception is recorded in the
added info.  This is the same approach used by Python 3's
`__context__` attribute[3].  This was brought to my attention in a
post by Mike Meyer[4], in a thread about having bracket not suppress
the original exception[5].

This doesn't directly resolve issue #2, due to this backwards
compatibility.  With the earlier example, a `catch` handler for
`InformativeErrorMessage` won't be invoked, because it isn't the
exception being rethrown.  This can be resolved by having a variant of
catch which instead throws the original exception.  This might be a
good default for finalization handlers like `bracket` and `finally`.

Asynchronous Exceptions
-----------------------

Currently, the only reliable way to catch exceptions, ignoring async
exceptions, is to fork a new thread.  This is the approach used by the
enclosed-exceptions[6] package.  I think it's quite ugly that we need
to go to such lengths due to the lack of one bit of information about
the exception!  This would resolve ghc trac #5902[7].

base-4.7 added the `SomeAsyncException` type, but this doesn't enforce
anything.  Any exception can be thrown as a sync or async exception.
Instead, we ought to have a reliable way to know if an exception is
synchronous or asynchronous.  Here's what this would look like:

    data IsAsync = IsAsync
        deriving (Typeable, Show)

    instance ExceptionInfo IsAsync where
        displayExceptionInfo IsAsync = "thrown asynchronously"

    throwTo :: Exception e => ThreadId -> e -> IO ()
    throwTo tid = oldThrowTo tid . addExceptionInfo IsAsync

The details of this get a bit tricky: What happens if `throwIO` is
used to rethrow a `SomeException` which has this `IsAsync` flag set?
I'm going to leave out my thoughts on this for now as the interactions
between unsafePerformIO and the concept of "rethrowing" async
exceptions.  Such details are explained in a post by Edsko de Vries[8]
and ghc trac #2558[9].

Issue: fromException loses info
===============================

I can think of one main non-ideal aspect of this proposal:

Currently, the `toException` and `fromException` methods usually form
a prism.  In other words, when `fromException` yields a `Just`, you
should get the same `SomeException` when using `toException` on that
value.

For example,

    fail "testing 1 2 3" `catch` \(ex :: SomeException) -> throwIO ex

is equivalent to

    fail "testing 3 4 5" `catch` \(ex :: IOError) -> throwIO ex

However, with exception info added to just `SomeException`, and no
changes to existing `Exception` instances, this
doesn't hold.  Exceptions caught as a specific exception type get
rethrown with less information.

One resolution to this is be to add `[SomeExceptionInfo]` as a field
to existing `Exception` instances.  This would require the use of
non-default implementations of the `toException` and `fromException`
instances.

Another approach is to have variants of `catch` and `throw` which also
pass around the `[SomeExceptionInfo]`.

[1] https://hackage.haskell.org/package/base-4.8.0.0/docs/GHC-Stack.html#currentCallStack
[2] https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack/ImplicitLocations
[3] https://www.python.org/dev/peps/pep-3134/
[4] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114987.html
[5] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114986.html
[6] https://hackage.haskell.org/package/enclosed-exceptions
[7] https://ghc.haskell.org/trac/ghc/ticket/5902
[8] http://www.edsko.net/2013/06/11/throwTo/
[9] https://ghc.haskell.org/trac/ghc/ticket/2558

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries




_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Michael Snoyman
The typical case for wanting to distinguish sync from async exceptions that I'm aware of is using timeout, e.g.:

    timeout (try someAction :: IO (Either SomeException SomeActionResult)

A more concrete example is available in the second code snippet at [1]. 


On Tue, Apr 21, 2015 at 8:27 AM John Lato <[hidden email]> wrote:

I don't have a strong opinion towards this proposal.  It definitely seems useful, but I think most of the uses would be either somewhat niche in practice, or better addressed by other mechanisms (the call stack stuff).

However I am opposed to adding IsAsync, because I do not think it is useful. In every case I know where a user wants to differentiate async exceptions, what they actually want is to differentiate RTS control exceptions from everything else.  I think the proposal would easily accommodate that, and you could have IsAsync too if there is a use case for it.


On Thu, Apr 16, 2015, 8:08 PM Carter Schonwald <[hidden email]> wrote:
if you can patch prelude error to include stack traces, i will owe you a >=1 beer each at the next two icfps. Thats all i want for christmas. :)

i can't speak for how a different patch might work out, because thats not what I'd tried at the time. If you have a go, please share the results!
-Carter

On Wed, Apr 15, 2015 at 12:22 AM, Michael Sloan <[hidden email]> wrote:
Hi Carter!


I haven't tried this as a patch to base, but I'm certain that the core of the proposal has no extra dependencies.  Note that the proposal isn't about stack traces in particular - that's just one application of being able to throw exceptions with extra information.

Even if `throwTo` isn't modified to throw exceptions with stack traces, this functionality could be provided outside of `Control.Exception` (though, that does seem like the right place to put it).  I'm surprised that the circularity was so problematic, though.  Why isn't it sufficient to have an hs-boot file for `GHC.Stack`, which exports `currentCallStack`?

-Michael

On Tue, Apr 14, 2015 at 7:55 PM, Carter Schonwald <[hidden email]> wrote:
Hey Michael,
I actually proposed something along these lines that got OK'd by libraries early this past fall, the main challenge we hit was actually doing the enginering to add the stack traces to exceptions! theres some nasty module cycles in base that happen when you try to weave things around so that the standard error "message here" call includes some stack trace info. Have you tried to do that simple starter patch to base? 

Chris Allen and I spent like 2 days trying to get it to work and just gave up because of the cycles. We (and others) would probably love some headway on that front.

Theres also some in progress work to use the dwarf debugging info data in >7.10 to provide useful stack traces in the default builds for GHC afaik, 'cause the stack trace functionality you're pointing at currenlty only work on profiled builds

cheers
-Carter

On Tue, Apr 14, 2015 at 2:38 PM, Michael Sloan <[hidden email]> wrote:
Control.Exception currently lacks a good way to supply extra
information along with exceptions.  For example, exceptions could be
thrown along with their callstack[1] or implicit stack[2], but we have
no generic way to include this information with exceptions.

Proposed Solution
=================

The proposed solution is to add a list of `SomeExceptionInfo` to the
`SomeException` datatype.  This list stores additional information
about the exception.  These `ExceptionInfo` instances use a mechanism
which is pretty much identical to the dynamic way the `Exception` type
works:

    data SomeException = forall e . Exception e =>
        SomeExceptionWithInfo e [SomeExceptionInfo]

    data SomeExceptionInfo = forall a . ExceptionInfo a =>
        SomeExceptionInfo a

    class Typeable a => ExceptionInfo a where
        displayExceptionInfo :: a -> String

    addExceptionInfo
        :: (ExceptionInfo a, Exception e)
        => a -> e -> SomeException
    addExceptionInfo x (toException -> SomeExceptionWithInfo e xs) =
        SomeExceptionWithInfo e (SomeExceptionInfo x : xs)

`ExceptionInfo` lacks the to / from functions that `Exception` has,
because I don't see much point in supporting a hierarchy for exception
info.  The `Typeable` superclass constraint supplies the necessary
casting.

`SomeExceptionInfo` could validly instead just use the constraint
`(Typeable a, Show a)`.  However, I believe it's good to have a new
class for this so that:

  * The user can specify a custom `displayExceptionInfo`
  implementation, for when this extra info is presented to the user.
  This function would be invoked by the `show` implementation for
  `SomeException`.

  * Types need to opt-in to be usable with `SomeExceptionInfo`.
  Similarly to exceptions, I imagine that a type with a
  `ExceptionInfo` instance won't be used for anything but acting as
  such an annotation.  Having a class for this allows you to ask GHCI
  about all in-scope exception info types via `:info ExceptionInfo`.

Backwards Compatibility
=======================

GHC 7.10 adds support for bidirectional pattern synonyms.  This means
that this change could be made without breaking code:

    pattern SomeException x <- SomeExceptionWithInfo x _ where
        SomeException x = SomeExceptionWithInfo x []

Note that consumers of this do not need to enable `-XPatternSynonyms`.

Applications
============

Callstacks
----------

As mentioned at the beginning, this can be used to add callstacks to
exceptions:

    newtype ExceptionCallStack =
        ExceptionCallStack { unExceptionCallStack :: [String] }
        deriving Typeable

    instance ExceptionInfo ExceptionCallStack where
        displayExceptionInfo = unlines . unExceptionCallStack

    throwIOWithStack :: Exception e => e -> IO a
    throwIOWithStack e = do
        stack <- currentCallStack
        if null stack
            then throwIO e
            else throwIO (addExceptionInfo (ExceptionCallStack stack) e)

I see little downside for making something like this the default
implementation `throwIO`.  Each rethrowing of the `SomeException`
would add an additional stacktrace to its annotation, much like the
output of `+RTS -xc`.  Unlike this debug output, though, the
stacktraces would be associated with the exception, rather than just
listing locations that exceptions were thrown.  This makes it
tractable to debug exceptions that occur in concurrent programs, or in
programs which frequently throw exceptions during normal functioning.

Throwing Exceptions in Handlers
-------------------------------

Example:

    main =
        throwIO InformativeErrorMessage `finally`
        throwIO ObscureCleanupIssue

While `InformativeErrorMessage` got thrown, the user doesn't see it,
since `ObscureCleanupIssue` is thrown instead.  This causes a few
issues:

1. If the exception is handled by the default handler and yielded to
   the user, then the more informative error is lost.

2. Callers who expect to catch the "Informative error message" won't
   run their handlers for this exception type.

Problem 1 can now easily be resolved by adding some info to the
exception:

    data ExceptionCause = ExceptionCause
        { unExceptionCause :: SomeException }
        deriving Typeable

    instance ExceptionInfo ExceptionCause where
        displayExceptionInfo fe =
            "thrown while handling " ++
            displayException (unExceptionCause fe)

    catch :: Exception e => IO a -> (e -> IO a) -> IO a
    catch f g = f `oldCatch` handler
      where
        handler ex = g ex `oldCatch` \(ex' :: SomeException) ->
            throwIO (addExceptionInfo info ex')
          where
            info = ExceptionCause (toException ex)

This implementation of `catch` is written in a backwards-compatible
way, such that the exception thrown during finalization is still the
one that gets rethrown.  The "original" exception is recorded in the
added info.  This is the same approach used by Python 3's
`__context__` attribute[3].  This was brought to my attention in a
post by Mike Meyer[4], in a thread about having bracket not suppress
the original exception[5].

This doesn't directly resolve issue #2, due to this backwards
compatibility.  With the earlier example, a `catch` handler for
`InformativeErrorMessage` won't be invoked, because it isn't the
exception being rethrown.  This can be resolved by having a variant of
catch which instead throws the original exception.  This might be a
good default for finalization handlers like `bracket` and `finally`.

Asynchronous Exceptions
-----------------------

Currently, the only reliable way to catch exceptions, ignoring async
exceptions, is to fork a new thread.  This is the approach used by the
enclosed-exceptions[6] package.  I think it's quite ugly that we need
to go to such lengths due to the lack of one bit of information about
the exception!  This would resolve ghc trac #5902[7].

base-4.7 added the `SomeAsyncException` type, but this doesn't enforce
anything.  Any exception can be thrown as a sync or async exception.
Instead, we ought to have a reliable way to know if an exception is
synchronous or asynchronous.  Here's what this would look like:

    data IsAsync = IsAsync
        deriving (Typeable, Show)

    instance ExceptionInfo IsAsync where
        displayExceptionInfo IsAsync = "thrown asynchronously"

    throwTo :: Exception e => ThreadId -> e -> IO ()
    throwTo tid = oldThrowTo tid . addExceptionInfo IsAsync

The details of this get a bit tricky: What happens if `throwIO` is
used to rethrow a `SomeException` which has this `IsAsync` flag set?
I'm going to leave out my thoughts on this for now as the interactions
between unsafePerformIO and the concept of "rethrowing" async
exceptions.  Such details are explained in a post by Edsko de Vries[8]
and ghc trac #2558[9].

Issue: fromException loses info
===============================

I can think of one main non-ideal aspect of this proposal:

Currently, the `toException` and `fromException` methods usually form
a prism.  In other words, when `fromException` yields a `Just`, you
should get the same `SomeException` when using `toException` on that
value.

For example,

    fail "testing 1 2 3" `catch` \(ex :: SomeException) -> throwIO ex

is equivalent to

    fail "testing 3 4 5" `catch` \(ex :: IOError) -> throwIO ex

However, with exception info added to just `SomeException`, and no
changes to existing `Exception` instances, this
doesn't hold.  Exceptions caught as a specific exception type get
rethrown with less information.

One resolution to this is be to add `[SomeExceptionInfo]` as a field
to existing `Exception` instances.  This would require the use of
non-default implementations of the `toException` and `fromException`
instances.

Another approach is to have variants of `catch` and `throw` which also
pass around the `[SomeExceptionInfo]`.

[1] https://hackage.haskell.org/package/base-4.8.0.0/docs/GHC-Stack.html#currentCallStack
[2] https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack/ImplicitLocations
[3] https://www.python.org/dev/peps/pep-3134/
[4] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114987.html
[5] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114986.html
[6] https://hackage.haskell.org/package/enclosed-exceptions
[7] https://ghc.haskell.org/trac/ghc/ticket/5902
[8] http://www.edsko.net/2013/06/11/throwTo/
[9] https://ghc.haskell.org/trac/ghc/ticket/2558

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries




_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

John Lato-2

That seems reasonable.  Objection withdrawn, which makes me neutral overall.


On Mon, Apr 20, 2015, 10:40 PM Michael Snoyman <[hidden email]> wrote:
The typical case for wanting to distinguish sync from async exceptions that I'm aware of is using timeout, e.g.:

    timeout (try someAction :: IO (Either SomeException SomeActionResult)

A more concrete example is available in the second code snippet at [1]. 


On Tue, Apr 21, 2015 at 8:27 AM John Lato <[hidden email]> wrote:

I don't have a strong opinion towards this proposal.  It definitely seems useful, but I think most of the uses would be either somewhat niche in practice, or better addressed by other mechanisms (the call stack stuff).

However I am opposed to adding IsAsync, because I do not think it is useful. In every case I know where a user wants to differentiate async exceptions, what they actually want is to differentiate RTS control exceptions from everything else.  I think the proposal would easily accommodate that, and you could have IsAsync too if there is a use case for it.


On Thu, Apr 16, 2015, 8:08 PM Carter Schonwald <[hidden email]> wrote:
if you can patch prelude error to include stack traces, i will owe you a >=1 beer each at the next two icfps. Thats all i want for christmas. :)

i can't speak for how a different patch might work out, because thats not what I'd tried at the time. If you have a go, please share the results!
-Carter

On Wed, Apr 15, 2015 at 12:22 AM, Michael Sloan <[hidden email]> wrote:
Hi Carter!


I haven't tried this as a patch to base, but I'm certain that the core of the proposal has no extra dependencies.  Note that the proposal isn't about stack traces in particular - that's just one application of being able to throw exceptions with extra information.

Even if `throwTo` isn't modified to throw exceptions with stack traces, this functionality could be provided outside of `Control.Exception` (though, that does seem like the right place to put it).  I'm surprised that the circularity was so problematic, though.  Why isn't it sufficient to have an hs-boot file for `GHC.Stack`, which exports `currentCallStack`?

-Michael

On Tue, Apr 14, 2015 at 7:55 PM, Carter Schonwald <[hidden email]> wrote:
Hey Michael,
I actually proposed something along these lines that got OK'd by libraries early this past fall, the main challenge we hit was actually doing the enginering to add the stack traces to exceptions! theres some nasty module cycles in base that happen when you try to weave things around so that the standard error "message here" call includes some stack trace info. Have you tried to do that simple starter patch to base? 

Chris Allen and I spent like 2 days trying to get it to work and just gave up because of the cycles. We (and others) would probably love some headway on that front.

Theres also some in progress work to use the dwarf debugging info data in >7.10 to provide useful stack traces in the default builds for GHC afaik, 'cause the stack trace functionality you're pointing at currenlty only work on profiled builds

cheers
-Carter

On Tue, Apr 14, 2015 at 2:38 PM, Michael Sloan <[hidden email]> wrote:
Control.Exception currently lacks a good way to supply extra
information along with exceptions.  For example, exceptions could be
thrown along with their callstack[1] or implicit stack[2], but we have
no generic way to include this information with exceptions.

Proposed Solution
=================

The proposed solution is to add a list of `SomeExceptionInfo` to the
`SomeException` datatype.  This list stores additional information
about the exception.  These `ExceptionInfo` instances use a mechanism
which is pretty much identical to the dynamic way the `Exception` type
works:

    data SomeException = forall e . Exception e =>
        SomeExceptionWithInfo e [SomeExceptionInfo]

    data SomeExceptionInfo = forall a . ExceptionInfo a =>
        SomeExceptionInfo a

    class Typeable a => ExceptionInfo a where
        displayExceptionInfo :: a -> String

    addExceptionInfo
        :: (ExceptionInfo a, Exception e)
        => a -> e -> SomeException
    addExceptionInfo x (toException -> SomeExceptionWithInfo e xs) =
        SomeExceptionWithInfo e (SomeExceptionInfo x : xs)

`ExceptionInfo` lacks the to / from functions that `Exception` has,
because I don't see much point in supporting a hierarchy for exception
info.  The `Typeable` superclass constraint supplies the necessary
casting.

`SomeExceptionInfo` could validly instead just use the constraint
`(Typeable a, Show a)`.  However, I believe it's good to have a new
class for this so that:

  * The user can specify a custom `displayExceptionInfo`
  implementation, for when this extra info is presented to the user.
  This function would be invoked by the `show` implementation for
  `SomeException`.

  * Types need to opt-in to be usable with `SomeExceptionInfo`.
  Similarly to exceptions, I imagine that a type with a
  `ExceptionInfo` instance won't be used for anything but acting as
  such an annotation.  Having a class for this allows you to ask GHCI
  about all in-scope exception info types via `:info ExceptionInfo`.

Backwards Compatibility
=======================

GHC 7.10 adds support for bidirectional pattern synonyms.  This means
that this change could be made without breaking code:

    pattern SomeException x <- SomeExceptionWithInfo x _ where
        SomeException x = SomeExceptionWithInfo x []

Note that consumers of this do not need to enable `-XPatternSynonyms`.

Applications
============

Callstacks
----------

As mentioned at the beginning, this can be used to add callstacks to
exceptions:

    newtype ExceptionCallStack =
        ExceptionCallStack { unExceptionCallStack :: [String] }
        deriving Typeable

    instance ExceptionInfo ExceptionCallStack where
        displayExceptionInfo = unlines . unExceptionCallStack

    throwIOWithStack :: Exception e => e -> IO a
    throwIOWithStack e = do
        stack <- currentCallStack
        if null stack
            then throwIO e
            else throwIO (addExceptionInfo (ExceptionCallStack stack) e)

I see little downside for making something like this the default
implementation `throwIO`.  Each rethrowing of the `SomeException`
would add an additional stacktrace to its annotation, much like the
output of `+RTS -xc`.  Unlike this debug output, though, the
stacktraces would be associated with the exception, rather than just
listing locations that exceptions were thrown.  This makes it
tractable to debug exceptions that occur in concurrent programs, or in
programs which frequently throw exceptions during normal functioning.

Throwing Exceptions in Handlers
-------------------------------

Example:

    main =
        throwIO InformativeErrorMessage `finally`
        throwIO ObscureCleanupIssue

While `InformativeErrorMessage` got thrown, the user doesn't see it,
since `ObscureCleanupIssue` is thrown instead.  This causes a few
issues:

1. If the exception is handled by the default handler and yielded to
   the user, then the more informative error is lost.

2. Callers who expect to catch the "Informative error message" won't
   run their handlers for this exception type.

Problem 1 can now easily be resolved by adding some info to the
exception:

    data ExceptionCause = ExceptionCause
        { unExceptionCause :: SomeException }
        deriving Typeable

    instance ExceptionInfo ExceptionCause where
        displayExceptionInfo fe =
            "thrown while handling " ++
            displayException (unExceptionCause fe)

    catch :: Exception e => IO a -> (e -> IO a) -> IO a
    catch f g = f `oldCatch` handler
      where
        handler ex = g ex `oldCatch` \(ex' :: SomeException) ->
            throwIO (addExceptionInfo info ex')
          where
            info = ExceptionCause (toException ex)

This implementation of `catch` is written in a backwards-compatible
way, such that the exception thrown during finalization is still the
one that gets rethrown.  The "original" exception is recorded in the
added info.  This is the same approach used by Python 3's
`__context__` attribute[3].  This was brought to my attention in a
post by Mike Meyer[4], in a thread about having bracket not suppress
the original exception[5].

This doesn't directly resolve issue #2, due to this backwards
compatibility.  With the earlier example, a `catch` handler for
`InformativeErrorMessage` won't be invoked, because it isn't the
exception being rethrown.  This can be resolved by having a variant of
catch which instead throws the original exception.  This might be a
good default for finalization handlers like `bracket` and `finally`.

Asynchronous Exceptions
-----------------------

Currently, the only reliable way to catch exceptions, ignoring async
exceptions, is to fork a new thread.  This is the approach used by the
enclosed-exceptions[6] package.  I think it's quite ugly that we need
to go to such lengths due to the lack of one bit of information about
the exception!  This would resolve ghc trac #5902[7].

base-4.7 added the `SomeAsyncException` type, but this doesn't enforce
anything.  Any exception can be thrown as a sync or async exception.
Instead, we ought to have a reliable way to know if an exception is
synchronous or asynchronous.  Here's what this would look like:

    data IsAsync = IsAsync
        deriving (Typeable, Show)

    instance ExceptionInfo IsAsync where
        displayExceptionInfo IsAsync = "thrown asynchronously"

    throwTo :: Exception e => ThreadId -> e -> IO ()
    throwTo tid = oldThrowTo tid . addExceptionInfo IsAsync

The details of this get a bit tricky: What happens if `throwIO` is
used to rethrow a `SomeException` which has this `IsAsync` flag set?
I'm going to leave out my thoughts on this for now as the interactions
between unsafePerformIO and the concept of "rethrowing" async
exceptions.  Such details are explained in a post by Edsko de Vries[8]
and ghc trac #2558[9].

Issue: fromException loses info
===============================

I can think of one main non-ideal aspect of this proposal:

Currently, the `toException` and `fromException` methods usually form
a prism.  In other words, when `fromException` yields a `Just`, you
should get the same `SomeException` when using `toException` on that
value.

For example,

    fail "testing 1 2 3" `catch` \(ex :: SomeException) -> throwIO ex

is equivalent to

    fail "testing 3 4 5" `catch` \(ex :: IOError) -> throwIO ex

However, with exception info added to just `SomeException`, and no
changes to existing `Exception` instances, this
doesn't hold.  Exceptions caught as a specific exception type get
rethrown with less information.

One resolution to this is be to add `[SomeExceptionInfo]` as a field
to existing `Exception` instances.  This would require the use of
non-default implementations of the `toException` and `fromException`
instances.

Another approach is to have variants of `catch` and `throw` which also
pass around the `[SomeExceptionInfo]`.

[1] https://hackage.haskell.org/package/base-4.8.0.0/docs/GHC-Stack.html#currentCallStack
[2] https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack/ImplicitLocations
[3] https://www.python.org/dev/peps/pep-3134/
[4] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114987.html
[5] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114986.html
[6] https://hackage.haskell.org/package/enclosed-exceptions
[7] https://ghc.haskell.org/trac/ghc/ticket/5902
[8] http://www.edsko.net/2013/06/11/throwTo/
[9] https://ghc.haskell.org/trac/ghc/ticket/2558

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries




_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Yitzchak Gale
In reply to this post by Michael Snoyman
Michael Snoyman wrote:
> Sorry to confuse this thread with a second Michael.
>
> 1) there's a very good use case that's currently excluded by the
> API, and (2) Michael Sloan figured out some great ways to minimize the
> breakage. You could even argue that this proposal has *no* API breakage.

Perhaps I missed it, but wouldn't this create major breakage
across hackage for 7.8 and before?

We will certainly be using 7.8 for years to come, at least for
older versions of our products that we must continue to
support. In fact, we are only now able to reduce
our use of 7.6 to a fairly low (but non-zero) level.

This cycle will likely be even longer than usual for the
7.10 upgrade due to the inclusion of the FTP breaking
changes which make upgrading much more difficult.

I certainly hope that most of the ecosystem will continue
to support GHC versions going back at least one or two
major versions, as it always has in the past.

All that said, I am in favor of this change - it is a great
design. But I would strongly oppose doing it immediately
unless there is some plan to allow continued support
of recent pre-7.10 GHC versions in the ecosystem.

Thanks,
Yitz ("you can call me Michael")
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Michael Snoyman


On Tue, Apr 21, 2015 at 12:05 PM Yitzchak Gale <[hidden email]> wrote:
Michael Snoyman wrote:
> Sorry to confuse this thread with a second Michael.
>
> 1) there's a very good use case that's currently excluded by the
> API, and (2) Michael Sloan figured out some great ways to minimize the
> breakage. You could even argue that this proposal has *no* API breakage.

Perhaps I missed it, but wouldn't this create major breakage
across hackage for 7.8 and before?

We will certainly be using 7.8 for years to come, at least for
older versions of our products that we must continue to
support. In fact, we are only now able to reduce
our use of 7.6 to a fairly low (but non-zero) level.

This cycle will likely be even longer than usual for the
7.10 upgrade due to the inclusion of the FTP breaking
changes which make upgrading much more difficult.

I certainly hope that most of the ecosystem will continue
to support GHC versions going back at least one or two
major versions, as it always has in the past.

All that said, I am in favor of this change - it is a great
design. But I would strongly oppose doing it immediately
unless there is some plan to allow continued support
of recent pre-7.10 GHC versions in the ecosystem.

Thanks,
Yitz ("you can call me Michael")

I may also be missing something here, but my read of Michael Sloan's proposal would mean that code written against the GHC 7.8/7.10 (and prior) API would continue to compile against GHC 7.12 and forward. It's true that code using the new functionality added in 7.12 would be unable to compile with 7.10 and earlier, but that's always the case when expanding the API (making this backwards compatible, or in PVP terms a minor version bump).

The only catch here is that the proposal simulates the old API by using pattern synonyms. That means that the data types have in fact changed. But I'm so far unable to come up with an example that compiles with 7.8/7.10 but not with this new API (with the caveat that I'm no expert in pattern synonyms and therefore I may be misunderstanding something).

I may also not be addressing what you're thinking of as the "major breakage." If so, can you clarify?

Michael

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Yitzchak Gale
Michael Snoyman wrote:
> I may also be missing something here, but my read of Michael Sloan's
> proposal would mean that code written against the GHC 7.8/7.10 (and prior)
> API would continue to compile against GHC 7.12 and forward. It's true that
> code using the new functionality added in 7.12 would be unable to compile
> with 7.10 and earlier, but that's always the case when expanding the API
> (making this backwards compatible, or in PVP terms a minor version bump)...
>
> I may also not be addressing what you're thinking of as the "major
> breakage." If so, can you clarify?

You're right, "major breakage" was not the right term.

What's worrying me is that since so many libraries depend on exceptions,
a sudden breaking change like this would prevent new versions of
almost any library from being used with 7.8. So, for example, critical
security fixes could not be applied.

In short, it would make 7.8 very fragile or even unusable very
quickly, which would indeed be "major breakage" for those of
us who will be needing to use it for the medium term.

Is there some way that backwards compatibility could somehow
be achieved without the requirement of a new extension like
PatternSynonyms, even though that might be the prettiest way?

One obvious way would be to use a new name for the new
SomeException, or perhaps just a different module name,
and export only a smart constructor for the new type. That
way there could be a compatibility library in which the smart
constructor discards the ExceptionInfo and returns the old type.
That would work going way back.

Or perhaps something could be done with other fancier
type features already present in 7.8, in a way that would
then allow us to move to Michael Sl.'s nice PatternSynonym
solution in 7.14.

Thanks,
Yitz
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Michael Snoyman


On Tue, Apr 21, 2015 at 3:48 PM Yitzchak Gale <[hidden email]> wrote:
Michael Snoyman wrote:
> I may also be missing something here, but my read of Michael Sloan's
> proposal would mean that code written against the GHC 7.8/7.10 (and prior)
> API would continue to compile against GHC 7.12 and forward. It's true that
> code using the new functionality added in 7.12 would be unable to compile
> with 7.10 and earlier, but that's always the case when expanding the API
> (making this backwards compatible, or in PVP terms a minor version bump)...
>
> I may also not be addressing what you're thinking of as the "major
> breakage." If so, can you clarify?

You're right, "major breakage" was not the right term.

What's worrying me is that since so many libraries depend on exceptions,
a sudden breaking change like this would prevent new versions of
almost any library from being used with 7.8. So, for example, critical
security fixes could not be applied.

In short, it would make 7.8 very fragile or even unusable very
quickly, which would indeed be "major breakage" for those of
us who will be needing to use it for the medium term.

Is there some way that backwards compatibility could somehow
be achieved without the requirement of a new extension like
PatternSynonyms, even though that might be the prettiest way?

One obvious way would be to use a new name for the new
SomeException, or perhaps just a different module name,
and export only a smart constructor for the new type. That
way there could be a compatibility library in which the smart
constructor discards the ExceptionInfo and returns the old type.
That would work going way back.

Or perhaps something could be done with other fancier
type features already present in 7.8, in a way that would
then allow us to move to Michael Sl.'s nice PatternSynonym
solution in 7.14.

Thanks,
Yitz


Can you give an example of a concrete problem you're expecting to run into? I'm not seeing it. End users aren't using pattern synonyms in this proposal, it's an implementation detail of Control.Exception.

MIchael 

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Yitzchak Gale
Michael Snoyman wrote:
> Can you give an example of a concrete problem you're
> expecting to run into?

Package foo uploads a new version with a critical bug fix.
As is often the case, this new version also supports updated
dependencies, including exceptions. The new exceptions
breaks the old SomeException type, so foo is forced to
specify a lower bound that excludes the old exceptions.

I depend on foo and I need to compile using GHC 7.8.
Can I get this critical bug fix for foo?

We can't always prevent this kind of scenario.
And when it does happen in an isolated case, there
are work-arounds. But for a ubiquitous dependency
like exceptions, this single breaking change would
effectively block future upgrades of a significant proportion
of Hackage for GHC 7.8.
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Greg Weber
In reply to this post by Michael Sloan

On Tue, Apr 14, 2015 at 11:38 AM, Michael Sloan <[hidden email]> wrote:
    data SomeException = forall e . Exception e =>
        SomeExceptionWithInfo e [SomeExceptionInfo]

    data SomeExceptionInfo = forall a . ExceptionInfo a =>
        SomeExceptionInfo a

Is it necessary for SomeExceptionWithInfo to have a list of a forall data type?
Are Exceptions really that mysterious, or can we more concretely describe the information that should be attached to an exception?

    SomeExceptionWithInfo e IsAsync CallStack ImplicitStack

I am still open to the idea of adding a forall data scratchpad, but can we at least try to specify some standard fields?

    SomeExceptionWithInfo e IsAsync CallStack ImplicitStack [SomeExceptionInfo]

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Michael Snoyman
In reply to this post by Yitzchak Gale


On Tue, Apr 21, 2015 at 5:56 PM Yitzchak Gale <[hidden email]> wrote:
Michael Snoyman wrote:
> Can you give an example of a concrete problem you're
> expecting to run into?

Package foo uploads a new version with a critical bug fix.
As is often the case, this new version also supports updated
dependencies, including exceptions. The new exceptions
breaks the old SomeException type, so foo is forced to
specify a lower bound that excludes the old exceptions.


But that's the scenario I'm asking for more information on. Can you clarify what you're describing here? I'm not seeing a situation where an author couldn't easily be compatible with GHC <=7.8 by sticking to the old API?
 
I depend on foo and I need to compile using GHC 7.8.
Can I get this critical bug fix for foo?

We can't always prevent this kind of scenario.
And when it does happen in an isolated case, there
are work-arounds. But for a ubiquitous dependency
like exceptions, this single breaking change would
effectively block future upgrades of a significant proportion
of Hackage for GHC 7.8.

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Felipe Lessa
I'm +1 on this proposal.

On 21-04-2015 12:52, Michael Snoyman wrote:

> On Tue, Apr 21, 2015 at 5:56 PM Yitzchak Gale <[hidden email]
> <mailto:[hidden email]>> wrote:
>
>     Michael Snoyman wrote:
>     > Can you give an example of a concrete problem you're
>     > expecting to run into?
>
>     Package foo uploads a new version with a critical bug fix.
>     As is often the case, this new version also supports updated
>     dependencies, including exceptions. The new exceptions
>     breaks the old SomeException type, so foo is forced to
>     specify a lower bound that excludes the old exceptions.
>
>
> But that's the scenario I'm asking for more information on. Can you
> clarify what you're describing here? I'm not seeing a situation where an
> author couldn't easily be compatible with GHC <=7.8 by sticking to the
> old API?
I think Michael Gale is thinking about an author that may end up using
the new SomeExceptionWithInfo constructor for some reason, thus leaving
older GHCs out of any new updates.  However, IIUC, this would cause
problems for GHC < 7.12, not only GHC < 7.10, since you won't be able to
use GHC 7.12's base package on GHC 7.10.  So perhaps I'm
misunderstanding the issue as well.

Cheers,

--
Michael Lessa.


_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

signature.asc (836 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Michael Sloan
In reply to this post by Carter Schonwald
On Thu, Apr 16, 2015 at 8:08 PM, Carter Schonwald <[hidden email]> wrote:
if you can patch prelude error to include stack traces, i will owe you a >=1 beer each at the next two icfps. Thats all i want for christmas. :)
 
Sounds good!  No promises, but I'll be giving this a try soon.  Looking forward to ICFP beers either way :D

i can't speak for how a different patch might work out, because thats not what I'd tried at the time. If you have a go, please share the results!
-Carter

On Wed, Apr 15, 2015 at 12:22 AM, Michael Sloan <[hidden email]> wrote:
Hi Carter!


I haven't tried this as a patch to base, but I'm certain that the core of the proposal has no extra dependencies.  Note that the proposal isn't about stack traces in particular - that's just one application of being able to throw exceptions with extra information.

Even if `throwTo` isn't modified to throw exceptions with stack traces, this functionality could be provided outside of `Control.Exception` (though, that does seem like the right place to put it).  I'm surprised that the circularity was so problematic, though.  Why isn't it sufficient to have an hs-boot file for `GHC.Stack`, which exports `currentCallStack`?

-Michael

On Tue, Apr 14, 2015 at 7:55 PM, Carter Schonwald <[hidden email]> wrote:
Hey Michael,
I actually proposed something along these lines that got OK'd by libraries early this past fall, the main challenge we hit was actually doing the enginering to add the stack traces to exceptions! theres some nasty module cycles in base that happen when you try to weave things around so that the standard error "message here" call includes some stack trace info. Have you tried to do that simple starter patch to base? 

Chris Allen and I spent like 2 days trying to get it to work and just gave up because of the cycles. We (and others) would probably love some headway on that front.

Theres also some in progress work to use the dwarf debugging info data in >7.10 to provide useful stack traces in the default builds for GHC afaik, 'cause the stack trace functionality you're pointing at currenlty only work on profiled builds

cheers
-Carter

On Tue, Apr 14, 2015 at 2:38 PM, Michael Sloan <[hidden email]> wrote:
Control.Exception currently lacks a good way to supply extra
information along with exceptions.  For example, exceptions could be
thrown along with their callstack[1] or implicit stack[2], but we have
no generic way to include this information with exceptions.

Proposed Solution
=================

The proposed solution is to add a list of `SomeExceptionInfo` to the
`SomeException` datatype.  This list stores additional information
about the exception.  These `ExceptionInfo` instances use a mechanism
which is pretty much identical to the dynamic way the `Exception` type
works:

    data SomeException = forall e . Exception e =>
        SomeExceptionWithInfo e [SomeExceptionInfo]

    data SomeExceptionInfo = forall a . ExceptionInfo a =>
        SomeExceptionInfo a

    class Typeable a => ExceptionInfo a where
        displayExceptionInfo :: a -> String

    addExceptionInfo
        :: (ExceptionInfo a, Exception e)
        => a -> e -> SomeException
    addExceptionInfo x (toException -> SomeExceptionWithInfo e xs) =
        SomeExceptionWithInfo e (SomeExceptionInfo x : xs)

`ExceptionInfo` lacks the to / from functions that `Exception` has,
because I don't see much point in supporting a hierarchy for exception
info.  The `Typeable` superclass constraint supplies the necessary
casting.

`SomeExceptionInfo` could validly instead just use the constraint
`(Typeable a, Show a)`.  However, I believe it's good to have a new
class for this so that:

  * The user can specify a custom `displayExceptionInfo`
  implementation, for when this extra info is presented to the user.
  This function would be invoked by the `show` implementation for
  `SomeException`.

  * Types need to opt-in to be usable with `SomeExceptionInfo`.
  Similarly to exceptions, I imagine that a type with a
  `ExceptionInfo` instance won't be used for anything but acting as
  such an annotation.  Having a class for this allows you to ask GHCI
  about all in-scope exception info types via `:info ExceptionInfo`.

Backwards Compatibility
=======================

GHC 7.10 adds support for bidirectional pattern synonyms.  This means
that this change could be made without breaking code:

    pattern SomeException x <- SomeExceptionWithInfo x _ where
        SomeException x = SomeExceptionWithInfo x []

Note that consumers of this do not need to enable `-XPatternSynonyms`.

Applications
============

Callstacks
----------

As mentioned at the beginning, this can be used to add callstacks to
exceptions:

    newtype ExceptionCallStack =
        ExceptionCallStack { unExceptionCallStack :: [String] }
        deriving Typeable

    instance ExceptionInfo ExceptionCallStack where
        displayExceptionInfo = unlines . unExceptionCallStack

    throwIOWithStack :: Exception e => e -> IO a
    throwIOWithStack e = do
        stack <- currentCallStack
        if null stack
            then throwIO e
            else throwIO (addExceptionInfo (ExceptionCallStack stack) e)

I see little downside for making something like this the default
implementation `throwIO`.  Each rethrowing of the `SomeException`
would add an additional stacktrace to its annotation, much like the
output of `+RTS -xc`.  Unlike this debug output, though, the
stacktraces would be associated with the exception, rather than just
listing locations that exceptions were thrown.  This makes it
tractable to debug exceptions that occur in concurrent programs, or in
programs which frequently throw exceptions during normal functioning.

Throwing Exceptions in Handlers
-------------------------------

Example:

    main =
        throwIO InformativeErrorMessage `finally`
        throwIO ObscureCleanupIssue

While `InformativeErrorMessage` got thrown, the user doesn't see it,
since `ObscureCleanupIssue` is thrown instead.  This causes a few
issues:

1. If the exception is handled by the default handler and yielded to
   the user, then the more informative error is lost.

2. Callers who expect to catch the "Informative error message" won't
   run their handlers for this exception type.

Problem 1 can now easily be resolved by adding some info to the
exception:

    data ExceptionCause = ExceptionCause
        { unExceptionCause :: SomeException }
        deriving Typeable

    instance ExceptionInfo ExceptionCause where
        displayExceptionInfo fe =
            "thrown while handling " ++
            displayException (unExceptionCause fe)

    catch :: Exception e => IO a -> (e -> IO a) -> IO a
    catch f g = f `oldCatch` handler
      where
        handler ex = g ex `oldCatch` \(ex' :: SomeException) ->
            throwIO (addExceptionInfo info ex')
          where
            info = ExceptionCause (toException ex)

This implementation of `catch` is written in a backwards-compatible
way, such that the exception thrown during finalization is still the
one that gets rethrown.  The "original" exception is recorded in the
added info.  This is the same approach used by Python 3's
`__context__` attribute[3].  This was brought to my attention in a
post by Mike Meyer[4], in a thread about having bracket not suppress
the original exception[5].

This doesn't directly resolve issue #2, due to this backwards
compatibility.  With the earlier example, a `catch` handler for
`InformativeErrorMessage` won't be invoked, because it isn't the
exception being rethrown.  This can be resolved by having a variant of
catch which instead throws the original exception.  This might be a
good default for finalization handlers like `bracket` and `finally`.

Asynchronous Exceptions
-----------------------

Currently, the only reliable way to catch exceptions, ignoring async
exceptions, is to fork a new thread.  This is the approach used by the
enclosed-exceptions[6] package.  I think it's quite ugly that we need
to go to such lengths due to the lack of one bit of information about
the exception!  This would resolve ghc trac #5902[7].

base-4.7 added the `SomeAsyncException` type, but this doesn't enforce
anything.  Any exception can be thrown as a sync or async exception.
Instead, we ought to have a reliable way to know if an exception is
synchronous or asynchronous.  Here's what this would look like:

    data IsAsync = IsAsync
        deriving (Typeable, Show)

    instance ExceptionInfo IsAsync where
        displayExceptionInfo IsAsync = "thrown asynchronously"

    throwTo :: Exception e => ThreadId -> e -> IO ()
    throwTo tid = oldThrowTo tid . addExceptionInfo IsAsync

The details of this get a bit tricky: What happens if `throwIO` is
used to rethrow a `SomeException` which has this `IsAsync` flag set?
I'm going to leave out my thoughts on this for now as the interactions
between unsafePerformIO and the concept of "rethrowing" async
exceptions.  Such details are explained in a post by Edsko de Vries[8]
and ghc trac #2558[9].

Issue: fromException loses info
===============================

I can think of one main non-ideal aspect of this proposal:

Currently, the `toException` and `fromException` methods usually form
a prism.  In other words, when `fromException` yields a `Just`, you
should get the same `SomeException` when using `toException` on that
value.

For example,

    fail "testing 1 2 3" `catch` \(ex :: SomeException) -> throwIO ex

is equivalent to

    fail "testing 3 4 5" `catch` \(ex :: IOError) -> throwIO ex

However, with exception info added to just `SomeException`, and no
changes to existing `Exception` instances, this
doesn't hold.  Exceptions caught as a specific exception type get
rethrown with less information.

One resolution to this is be to add `[SomeExceptionInfo]` as a field
to existing `Exception` instances.  This would require the use of
non-default implementations of the `toException` and `fromException`
instances.

Another approach is to have variants of `catch` and `throw` which also
pass around the `[SomeExceptionInfo]`.

[1] https://hackage.haskell.org/package/base-4.8.0.0/docs/GHC-Stack.html#currentCallStack
[2] https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack/ImplicitLocations
[3] https://www.python.org/dev/peps/pep-3134/
[4] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114987.html
[5] https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114986.html
[6] https://hackage.haskell.org/package/enclosed-exceptions
[7] https://ghc.haskell.org/trac/ghc/ticket/5902
[8] http://www.edsko.net/2013/06/11/throwTo/
[9] https://ghc.haskell.org/trac/ghc/ticket/2558

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries






_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Michael Sloan
In reply to this post by Greg Weber

On Tue, Apr 21, 2015 at 8:23 AM, Greg Weber <[hidden email]> wrote:

On Tue, Apr 14, 2015 at 11:38 AM, Michael Sloan <[hidden email]> wrote:
    data SomeException = forall e . Exception e =>
        SomeExceptionWithInfo e [SomeExceptionInfo]

    data SomeExceptionInfo = forall a . ExceptionInfo a =>
        SomeExceptionInfo a

Is it necessary for SomeExceptionWithInfo to have a list of a forall data type?
Are Exceptions really that mysterious, or can we more concretely describe the information that should be attached to an exception?

    SomeExceptionWithInfo e IsAsync CallStack ImplicitStack

I did consider this option, but I think as soon as a fixed set is selected, someone's going to put something else in it.  Usually we wouldn't want to use such a 'dynamic' mechanism in Haskell, but it's appropriate for something so global as the type used to throw exceptions.
 
I am still open to the idea of adding a forall data scratchpad, but can we at least try to specify some standard fields?

    SomeExceptionWithInfo e IsAsync CallStack ImplicitStack [SomeExceptionInfo]

This is an interesting idea.  I particularly see value in having 'IsAsync' be a part of the Exception.  This is because `throwIO` / `throw` would need to set this to False when rethrowing async exceptions.

_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Evan Laforge
In reply to this post by Michael Sloan
Maybe I'm missing something, but isn't this already implemented?

https://phabricator.haskell.org/D578

On Tue, Apr 21, 2015 at 1:37 PM, Michael Sloan <[hidden email]> wrote:

> On Thu, Apr 16, 2015 at 8:08 PM, Carter Schonwald
> <[hidden email]> wrote:
>>
>> if you can patch prelude error to include stack traces, i will owe you a
>> >=1 beer each at the next two icfps. Thats all i want for christmas. :)
>
>
> Sounds good!  No promises, but I'll be giving this a try soon.  Looking
> forward to ICFP beers either way :D
>
>> i can't speak for how a different patch might work out, because thats not
>> what I'd tried at the time. If you have a go, please share the results!
>> -Carter
>>
>> On Wed, Apr 15, 2015 at 12:22 AM, Michael Sloan <[hidden email]> wrote:
>>>
>>> Hi Carter!
>>>
>>> Interesting!  This thread, right?
>>> https://mail.haskell.org/pipermail/libraries/2014-December/024429.html
>>>
>>> I haven't tried this as a patch to base, but I'm certain that the core of
>>> the proposal has no extra dependencies.  Note that the proposal isn't about
>>> stack traces in particular - that's just one application of being able to
>>> throw exceptions with extra information.
>>>
>>> Even if `throwTo` isn't modified to throw exceptions with stack traces,
>>> this functionality could be provided outside of `Control.Exception` (though,
>>> that does seem like the right place to put it).  I'm surprised that the
>>> circularity was so problematic, though.  Why isn't it sufficient to have an
>>> hs-boot file for `GHC.Stack`, which exports `currentCallStack`?
>>>
>>> -Michael
>>>
>>> On Tue, Apr 14, 2015 at 7:55 PM, Carter Schonwald
>>> <[hidden email]> wrote:
>>>>
>>>> Hey Michael,
>>>> I actually proposed something along these lines that got OK'd by
>>>> libraries early this past fall, the main challenge we hit was actually doing
>>>> the enginering to add the stack traces to exceptions! theres some nasty
>>>> module cycles in base that happen when you try to weave things around so
>>>> that the standard error "message here" call includes some stack trace info.
>>>> Have you tried to do that simple starter patch to base?
>>>>
>>>> Chris Allen and I spent like 2 days trying to get it to work and just
>>>> gave up because of the cycles. We (and others) would probably love some
>>>> headway on that front.
>>>>
>>>> Theres also some in progress work to use the dwarf debugging info data
>>>> in >7.10 to provide useful stack traces in the default builds for GHC afaik,
>>>> 'cause the stack trace functionality you're pointing at currenlty only work
>>>> on profiled builds
>>>>
>>>> cheers
>>>> -Carter
>>>>
>>>> On Tue, Apr 14, 2015 at 2:38 PM, Michael Sloan <[hidden email]>
>>>> wrote:
>>>>>
>>>>> Control.Exception currently lacks a good way to supply extra
>>>>> information along with exceptions.  For example, exceptions could be
>>>>> thrown along with their callstack[1] or implicit stack[2], but we have
>>>>> no generic way to include this information with exceptions.
>>>>>
>>>>> Proposed Solution
>>>>> =================
>>>>>
>>>>> The proposed solution is to add a list of `SomeExceptionInfo` to the
>>>>> `SomeException` datatype.  This list stores additional information
>>>>> about the exception.  These `ExceptionInfo` instances use a mechanism
>>>>> which is pretty much identical to the dynamic way the `Exception` type
>>>>> works:
>>>>>
>>>>>     data SomeException = forall e . Exception e =>
>>>>>         SomeExceptionWithInfo e [SomeExceptionInfo]
>>>>>
>>>>>     data SomeExceptionInfo = forall a . ExceptionInfo a =>
>>>>>         SomeExceptionInfo a
>>>>>
>>>>>     class Typeable a => ExceptionInfo a where
>>>>>         displayExceptionInfo :: a -> String
>>>>>
>>>>>     addExceptionInfo
>>>>>         :: (ExceptionInfo a, Exception e)
>>>>>         => a -> e -> SomeException
>>>>>     addExceptionInfo x (toException -> SomeExceptionWithInfo e xs) =
>>>>>         SomeExceptionWithInfo e (SomeExceptionInfo x : xs)
>>>>>
>>>>> `ExceptionInfo` lacks the to / from functions that `Exception` has,
>>>>> because I don't see much point in supporting a hierarchy for exception
>>>>> info.  The `Typeable` superclass constraint supplies the necessary
>>>>> casting.
>>>>>
>>>>> `SomeExceptionInfo` could validly instead just use the constraint
>>>>> `(Typeable a, Show a)`.  However, I believe it's good to have a new
>>>>> class for this so that:
>>>>>
>>>>>   * The user can specify a custom `displayExceptionInfo`
>>>>>   implementation, for when this extra info is presented to the user.
>>>>>   This function would be invoked by the `show` implementation for
>>>>>   `SomeException`.
>>>>>
>>>>>   * Types need to opt-in to be usable with `SomeExceptionInfo`.
>>>>>   Similarly to exceptions, I imagine that a type with a
>>>>>   `ExceptionInfo` instance won't be used for anything but acting as
>>>>>   such an annotation.  Having a class for this allows you to ask GHCI
>>>>>   about all in-scope exception info types via `:info ExceptionInfo`.
>>>>>
>>>>> Backwards Compatibility
>>>>> =======================
>>>>>
>>>>> GHC 7.10 adds support for bidirectional pattern synonyms.  This means
>>>>> that this change could be made without breaking code:
>>>>>
>>>>>     pattern SomeException x <- SomeExceptionWithInfo x _ where
>>>>>         SomeException x = SomeExceptionWithInfo x []
>>>>>
>>>>> Note that consumers of this do not need to enable `-XPatternSynonyms`.
>>>>>
>>>>> Applications
>>>>> ============
>>>>>
>>>>> Callstacks
>>>>> ----------
>>>>>
>>>>> As mentioned at the beginning, this can be used to add callstacks to
>>>>> exceptions:
>>>>>
>>>>>     newtype ExceptionCallStack =
>>>>>         ExceptionCallStack { unExceptionCallStack :: [String] }
>>>>>         deriving Typeable
>>>>>
>>>>>     instance ExceptionInfo ExceptionCallStack where
>>>>>         displayExceptionInfo = unlines . unExceptionCallStack
>>>>>
>>>>>     throwIOWithStack :: Exception e => e -> IO a
>>>>>     throwIOWithStack e = do
>>>>>         stack <- currentCallStack
>>>>>         if null stack
>>>>>             then throwIO e
>>>>>             else throwIO (addExceptionInfo (ExceptionCallStack stack)
>>>>> e)
>>>>>
>>>>> I see little downside for making something like this the default
>>>>> implementation `throwIO`.  Each rethrowing of the `SomeException`
>>>>> would add an additional stacktrace to its annotation, much like the
>>>>> output of `+RTS -xc`.  Unlike this debug output, though, the
>>>>> stacktraces would be associated with the exception, rather than just
>>>>> listing locations that exceptions were thrown.  This makes it
>>>>> tractable to debug exceptions that occur in concurrent programs, or in
>>>>> programs which frequently throw exceptions during normal functioning.
>>>>>
>>>>> Throwing Exceptions in Handlers
>>>>> -------------------------------
>>>>>
>>>>> Example:
>>>>>
>>>>>     main =
>>>>>         throwIO InformativeErrorMessage `finally`
>>>>>         throwIO ObscureCleanupIssue
>>>>>
>>>>> While `InformativeErrorMessage` got thrown, the user doesn't see it,
>>>>> since `ObscureCleanupIssue` is thrown instead.  This causes a few
>>>>> issues:
>>>>>
>>>>> 1. If the exception is handled by the default handler and yielded to
>>>>>    the user, then the more informative error is lost.
>>>>>
>>>>> 2. Callers who expect to catch the "Informative error message" won't
>>>>>    run their handlers for this exception type.
>>>>>
>>>>> Problem 1 can now easily be resolved by adding some info to the
>>>>> exception:
>>>>>
>>>>>     data ExceptionCause = ExceptionCause
>>>>>         { unExceptionCause :: SomeException }
>>>>>         deriving Typeable
>>>>>
>>>>>     instance ExceptionInfo ExceptionCause where
>>>>>         displayExceptionInfo fe =
>>>>>             "thrown while handling " ++
>>>>>             displayException (unExceptionCause fe)
>>>>>
>>>>>     catch :: Exception e => IO a -> (e -> IO a) -> IO a
>>>>>     catch f g = f `oldCatch` handler
>>>>>       where
>>>>>         handler ex = g ex `oldCatch` \(ex' :: SomeException) ->
>>>>>             throwIO (addExceptionInfo info ex')
>>>>>           where
>>>>>             info = ExceptionCause (toException ex)
>>>>>
>>>>> This implementation of `catch` is written in a backwards-compatible
>>>>> way, such that the exception thrown during finalization is still the
>>>>> one that gets rethrown.  The "original" exception is recorded in the
>>>>> added info.  This is the same approach used by Python 3's
>>>>> `__context__` attribute[3].  This was brought to my attention in a
>>>>> post by Mike Meyer[4], in a thread about having bracket not suppress
>>>>> the original exception[5].
>>>>>
>>>>> This doesn't directly resolve issue #2, due to this backwards
>>>>> compatibility.  With the earlier example, a `catch` handler for
>>>>> `InformativeErrorMessage` won't be invoked, because it isn't the
>>>>> exception being rethrown.  This can be resolved by having a variant of
>>>>> catch which instead throws the original exception.  This might be a
>>>>> good default for finalization handlers like `bracket` and `finally`.
>>>>>
>>>>> Asynchronous Exceptions
>>>>> -----------------------
>>>>>
>>>>> Currently, the only reliable way to catch exceptions, ignoring async
>>>>> exceptions, is to fork a new thread.  This is the approach used by the
>>>>> enclosed-exceptions[6] package.  I think it's quite ugly that we need
>>>>> to go to such lengths due to the lack of one bit of information about
>>>>> the exception!  This would resolve ghc trac #5902[7].
>>>>>
>>>>> base-4.7 added the `SomeAsyncException` type, but this doesn't enforce
>>>>> anything.  Any exception can be thrown as a sync or async exception.
>>>>> Instead, we ought to have a reliable way to know if an exception is
>>>>> synchronous or asynchronous.  Here's what this would look like:
>>>>>
>>>>>     data IsAsync = IsAsync
>>>>>         deriving (Typeable, Show)
>>>>>
>>>>>     instance ExceptionInfo IsAsync where
>>>>>         displayExceptionInfo IsAsync = "thrown asynchronously"
>>>>>
>>>>>     throwTo :: Exception e => ThreadId -> e -> IO ()
>>>>>     throwTo tid = oldThrowTo tid . addExceptionInfo IsAsync
>>>>>
>>>>> The details of this get a bit tricky: What happens if `throwIO` is
>>>>> used to rethrow a `SomeException` which has this `IsAsync` flag set?
>>>>> I'm going to leave out my thoughts on this for now as the interactions
>>>>> between unsafePerformIO and the concept of "rethrowing" async
>>>>> exceptions.  Such details are explained in a post by Edsko de Vries[8]
>>>>> and ghc trac #2558[9].
>>>>>
>>>>> Issue: fromException loses info
>>>>> ===============================
>>>>>
>>>>> I can think of one main non-ideal aspect of this proposal:
>>>>>
>>>>> Currently, the `toException` and `fromException` methods usually form
>>>>> a prism.  In other words, when `fromException` yields a `Just`, you
>>>>> should get the same `SomeException` when using `toException` on that
>>>>> value.
>>>>>
>>>>> For example,
>>>>>
>>>>>     fail "testing 1 2 3" `catch` \(ex :: SomeException) -> throwIO ex
>>>>>
>>>>> is equivalent to
>>>>>
>>>>>     fail "testing 3 4 5" `catch` \(ex :: IOError) -> throwIO ex
>>>>>
>>>>> However, with exception info added to just `SomeException`, and no
>>>>> changes to existing `Exception` instances, this
>>>>> doesn't hold.  Exceptions caught as a specific exception type get
>>>>> rethrown with less information.
>>>>>
>>>>> One resolution to this is be to add `[SomeExceptionInfo]` as a field
>>>>> to existing `Exception` instances.  This would require the use of
>>>>> non-default implementations of the `toException` and `fromException`
>>>>> instances.
>>>>>
>>>>> Another approach is to have variants of `catch` and `throw` which also
>>>>> pass around the `[SomeExceptionInfo]`.
>>>>>
>>>>> [1]
>>>>> https://hackage.haskell.org/package/base-4.8.0.0/docs/GHC-Stack.html#currentCallStack
>>>>> [2]
>>>>> https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack/ImplicitLocations
>>>>> [3] https://www.python.org/dev/peps/pep-3134/
>>>>> [4]
>>>>> https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114987.html
>>>>> [5]
>>>>> https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114986.html
>>>>> [6] https://hackage.haskell.org/package/enclosed-exceptions
>>>>> [7] https://ghc.haskell.org/trac/ghc/ticket/5902
>>>>> [8] http://www.edsko.net/2013/06/11/throwTo/
>>>>> [9] https://ghc.haskell.org/trac/ghc/ticket/2558
>>>>>
>>>>> _______________________________________________
>>>>> Libraries mailing list
>>>>> [hidden email]
>>>>> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>>>>>
>>>>
>>>
>>
>
>
> _______________________________________________
> Libraries mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>
_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
Reply | Threaded
Open this post in threaded view
|

Re: Proposal: Add exception info

Michael Sloan
Hmm, that patch doesn't appear to add stack traces to 'Prelude.error', which is what Carter wants here.  Also, I think it would be done with profiling callstacks rather than implicit callstacks.  But it's certainly also useful to have functions which do the same with implicit callstacks!

On Tue, Apr 21, 2015 at 1:55 PM, Evan Laforge <[hidden email]> wrote:
Maybe I'm missing something, but isn't this already implemented?

https://phabricator.haskell.org/D578

On Tue, Apr 21, 2015 at 1:37 PM, Michael Sloan <[hidden email]> wrote:
> On Thu, Apr 16, 2015 at 8:08 PM, Carter Schonwald
> <[hidden email]> wrote:
>>
>> if you can patch prelude error to include stack traces, i will owe you a
>> >=1 beer each at the next two icfps. Thats all i want for christmas. :)
>
>
> Sounds good!  No promises, but I'll be giving this a try soon.  Looking
> forward to ICFP beers either way :D
>
>> i can't speak for how a different patch might work out, because thats not
>> what I'd tried at the time. If you have a go, please share the results!
>> -Carter
>>
>> On Wed, Apr 15, 2015 at 12:22 AM, Michael Sloan <[hidden email]> wrote:
>>>
>>> Hi Carter!
>>>
>>> Interesting!  This thread, right?
>>> https://mail.haskell.org/pipermail/libraries/2014-December/024429.html
>>>
>>> I haven't tried this as a patch to base, but I'm certain that the core of
>>> the proposal has no extra dependencies.  Note that the proposal isn't about
>>> stack traces in particular - that's just one application of being able to
>>> throw exceptions with extra information.
>>>
>>> Even if `throwTo` isn't modified to throw exceptions with stack traces,
>>> this functionality could be provided outside of `Control.Exception` (though,
>>> that does seem like the right place to put it).  I'm surprised that the
>>> circularity was so problematic, though.  Why isn't it sufficient to have an
>>> hs-boot file for `GHC.Stack`, which exports `currentCallStack`?
>>>
>>> -Michael
>>>
>>> On Tue, Apr 14, 2015 at 7:55 PM, Carter Schonwald
>>> <[hidden email]> wrote:
>>>>
>>>> Hey Michael,
>>>> I actually proposed something along these lines that got OK'd by
>>>> libraries early this past fall, the main challenge we hit was actually doing
>>>> the enginering to add the stack traces to exceptions! theres some nasty
>>>> module cycles in base that happen when you try to weave things around so
>>>> that the standard error "message here" call includes some stack trace info.
>>>> Have you tried to do that simple starter patch to base?
>>>>
>>>> Chris Allen and I spent like 2 days trying to get it to work and just
>>>> gave up because of the cycles. We (and others) would probably love some
>>>> headway on that front.
>>>>
>>>> Theres also some in progress work to use the dwarf debugging info data
>>>> in >7.10 to provide useful stack traces in the default builds for GHC afaik,
>>>> 'cause the stack trace functionality you're pointing at currenlty only work
>>>> on profiled builds
>>>>
>>>> cheers
>>>> -Carter
>>>>
>>>> On Tue, Apr 14, 2015 at 2:38 PM, Michael Sloan <[hidden email]>
>>>> wrote:
>>>>>
>>>>> Control.Exception currently lacks a good way to supply extra
>>>>> information along with exceptions.  For example, exceptions could be
>>>>> thrown along with their callstack[1] or implicit stack[2], but we have
>>>>> no generic way to include this information with exceptions.
>>>>>
>>>>> Proposed Solution
>>>>> =================
>>>>>
>>>>> The proposed solution is to add a list of `SomeExceptionInfo` to the
>>>>> `SomeException` datatype.  This list stores additional information
>>>>> about the exception.  These `ExceptionInfo` instances use a mechanism
>>>>> which is pretty much identical to the dynamic way the `Exception` type
>>>>> works:
>>>>>
>>>>>     data SomeException = forall e . Exception e =>
>>>>>         SomeExceptionWithInfo e [SomeExceptionInfo]
>>>>>
>>>>>     data SomeExceptionInfo = forall a . ExceptionInfo a =>
>>>>>         SomeExceptionInfo a
>>>>>
>>>>>     class Typeable a => ExceptionInfo a where
>>>>>         displayExceptionInfo :: a -> String
>>>>>
>>>>>     addExceptionInfo
>>>>>         :: (ExceptionInfo a, Exception e)
>>>>>         => a -> e -> SomeException
>>>>>     addExceptionInfo x (toException -> SomeExceptionWithInfo e xs) =
>>>>>         SomeExceptionWithInfo e (SomeExceptionInfo x : xs)
>>>>>
>>>>> `ExceptionInfo` lacks the to / from functions that `Exception` has,
>>>>> because I don't see much point in supporting a hierarchy for exception
>>>>> info.  The `Typeable` superclass constraint supplies the necessary
>>>>> casting.
>>>>>
>>>>> `SomeExceptionInfo` could validly instead just use the constraint
>>>>> `(Typeable a, Show a)`.  However, I believe it's good to have a new
>>>>> class for this so that:
>>>>>
>>>>>   * The user can specify a custom `displayExceptionInfo`
>>>>>   implementation, for when this extra info is presented to the user.
>>>>>   This function would be invoked by the `show` implementation for
>>>>>   `SomeException`.
>>>>>
>>>>>   * Types need to opt-in to be usable with `SomeExceptionInfo`.
>>>>>   Similarly to exceptions, I imagine that a type with a
>>>>>   `ExceptionInfo` instance won't be used for anything but acting as
>>>>>   such an annotation.  Having a class for this allows you to ask GHCI
>>>>>   about all in-scope exception info types via `:info ExceptionInfo`.
>>>>>
>>>>> Backwards Compatibility
>>>>> =======================
>>>>>
>>>>> GHC 7.10 adds support for bidirectional pattern synonyms.  This means
>>>>> that this change could be made without breaking code:
>>>>>
>>>>>     pattern SomeException x <- SomeExceptionWithInfo x _ where
>>>>>         SomeException x = SomeExceptionWithInfo x []
>>>>>
>>>>> Note that consumers of this do not need to enable `-XPatternSynonyms`.
>>>>>
>>>>> Applications
>>>>> ============
>>>>>
>>>>> Callstacks
>>>>> ----------
>>>>>
>>>>> As mentioned at the beginning, this can be used to add callstacks to
>>>>> exceptions:
>>>>>
>>>>>     newtype ExceptionCallStack =
>>>>>         ExceptionCallStack { unExceptionCallStack :: [String] }
>>>>>         deriving Typeable
>>>>>
>>>>>     instance ExceptionInfo ExceptionCallStack where
>>>>>         displayExceptionInfo = unlines . unExceptionCallStack
>>>>>
>>>>>     throwIOWithStack :: Exception e => e -> IO a
>>>>>     throwIOWithStack e = do
>>>>>         stack <- currentCallStack
>>>>>         if null stack
>>>>>             then throwIO e
>>>>>             else throwIO (addExceptionInfo (ExceptionCallStack stack)
>>>>> e)
>>>>>
>>>>> I see little downside for making something like this the default
>>>>> implementation `throwIO`.  Each rethrowing of the `SomeException`
>>>>> would add an additional stacktrace to its annotation, much like the
>>>>> output of `+RTS -xc`.  Unlike this debug output, though, the
>>>>> stacktraces would be associated with the exception, rather than just
>>>>> listing locations that exceptions were thrown.  This makes it
>>>>> tractable to debug exceptions that occur in concurrent programs, or in
>>>>> programs which frequently throw exceptions during normal functioning.
>>>>>
>>>>> Throwing Exceptions in Handlers
>>>>> -------------------------------
>>>>>
>>>>> Example:
>>>>>
>>>>>     main =
>>>>>         throwIO InformativeErrorMessage `finally`
>>>>>         throwIO ObscureCleanupIssue
>>>>>
>>>>> While `InformativeErrorMessage` got thrown, the user doesn't see it,
>>>>> since `ObscureCleanupIssue` is thrown instead.  This causes a few
>>>>> issues:
>>>>>
>>>>> 1. If the exception is handled by the default handler and yielded to
>>>>>    the user, then the more informative error is lost.
>>>>>
>>>>> 2. Callers who expect to catch the "Informative error message" won't
>>>>>    run their handlers for this exception type.
>>>>>
>>>>> Problem 1 can now easily be resolved by adding some info to the
>>>>> exception:
>>>>>
>>>>>     data ExceptionCause = ExceptionCause
>>>>>         { unExceptionCause :: SomeException }
>>>>>         deriving Typeable
>>>>>
>>>>>     instance ExceptionInfo ExceptionCause where
>>>>>         displayExceptionInfo fe =
>>>>>             "thrown while handling " ++
>>>>>             displayException (unExceptionCause fe)
>>>>>
>>>>>     catch :: Exception e => IO a -> (e -> IO a) -> IO a
>>>>>     catch f g = f `oldCatch` handler
>>>>>       where
>>>>>         handler ex = g ex `oldCatch` \(ex' :: SomeException) ->
>>>>>             throwIO (addExceptionInfo info ex')
>>>>>           where
>>>>>             info = ExceptionCause (toException ex)
>>>>>
>>>>> This implementation of `catch` is written in a backwards-compatible
>>>>> way, such that the exception thrown during finalization is still the
>>>>> one that gets rethrown.  The "original" exception is recorded in the
>>>>> added info.  This is the same approach used by Python 3's
>>>>> `__context__` attribute[3].  This was brought to my attention in a
>>>>> post by Mike Meyer[4], in a thread about having bracket not suppress
>>>>> the original exception[5].
>>>>>
>>>>> This doesn't directly resolve issue #2, due to this backwards
>>>>> compatibility.  With the earlier example, a `catch` handler for
>>>>> `InformativeErrorMessage` won't be invoked, because it isn't the
>>>>> exception being rethrown.  This can be resolved by having a variant of
>>>>> catch which instead throws the original exception.  This might be a
>>>>> good default for finalization handlers like `bracket` and `finally`.
>>>>>
>>>>> Asynchronous Exceptions
>>>>> -----------------------
>>>>>
>>>>> Currently, the only reliable way to catch exceptions, ignoring async
>>>>> exceptions, is to fork a new thread.  This is the approach used by the
>>>>> enclosed-exceptions[6] package.  I think it's quite ugly that we need
>>>>> to go to such lengths due to the lack of one bit of information about
>>>>> the exception!  This would resolve ghc trac #5902[7].
>>>>>
>>>>> base-4.7 added the `SomeAsyncException` type, but this doesn't enforce
>>>>> anything.  Any exception can be thrown as a sync or async exception.
>>>>> Instead, we ought to have a reliable way to know if an exception is
>>>>> synchronous or asynchronous.  Here's what this would look like:
>>>>>
>>>>>     data IsAsync = IsAsync
>>>>>         deriving (Typeable, Show)
>>>>>
>>>>>     instance ExceptionInfo IsAsync where
>>>>>         displayExceptionInfo IsAsync = "thrown asynchronously"
>>>>>
>>>>>     throwTo :: Exception e => ThreadId -> e -> IO ()
>>>>>     throwTo tid = oldThrowTo tid . addExceptionInfo IsAsync
>>>>>
>>>>> The details of this get a bit tricky: What happens if `throwIO` is
>>>>> used to rethrow a `SomeException` which has this `IsAsync` flag set?
>>>>> I'm going to leave out my thoughts on this for now as the interactions
>>>>> between unsafePerformIO and the concept of "rethrowing" async
>>>>> exceptions.  Such details are explained in a post by Edsko de Vries[8]
>>>>> and ghc trac #2558[9].
>>>>>
>>>>> Issue: fromException loses info
>>>>> ===============================
>>>>>
>>>>> I can think of one main non-ideal aspect of this proposal:
>>>>>
>>>>> Currently, the `toException` and `fromException` methods usually form
>>>>> a prism.  In other words, when `fromException` yields a `Just`, you
>>>>> should get the same `SomeException` when using `toException` on that
>>>>> value.
>>>>>
>>>>> For example,
>>>>>
>>>>>     fail "testing 1 2 3" `catch` \(ex :: SomeException) -> throwIO ex
>>>>>
>>>>> is equivalent to
>>>>>
>>>>>     fail "testing 3 4 5" `catch` \(ex :: IOError) -> throwIO ex
>>>>>
>>>>> However, with exception info added to just `SomeException`, and no
>>>>> changes to existing `Exception` instances, this
>>>>> doesn't hold.  Exceptions caught as a specific exception type get
>>>>> rethrown with less information.
>>>>>
>>>>> One resolution to this is be to add `[SomeExceptionInfo]` as a field
>>>>> to existing `Exception` instances.  This would require the use of
>>>>> non-default implementations of the `toException` and `fromException`
>>>>> instances.
>>>>>
>>>>> Another approach is to have variants of `catch` and `throw` which also
>>>>> pass around the `[SomeExceptionInfo]`.
>>>>>
>>>>> [1]
>>>>> https://hackage.haskell.org/package/base-4.8.0.0/docs/GHC-Stack.html#currentCallStack
>>>>> [2]
>>>>> https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack/ImplicitLocations
>>>>> [3] https://www.python.org/dev/peps/pep-3134/
>>>>> [4]
>>>>> https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114987.html
>>>>> [5]
>>>>> https://mail.haskell.org/pipermail/haskell-cafe/2014-July/114986.html
>>>>> [6] https://hackage.haskell.org/package/enclosed-exceptions
>>>>> [7] https://ghc.haskell.org/trac/ghc/ticket/5902
>>>>> [8] http://www.edsko.net/2013/06/11/throwTo/
>>>>> [9] https://ghc.haskell.org/trac/ghc/ticket/2558
>>>>>
>>>>> _______________________________________________
>>>>> Libraries mailing list
>>>>> [hidden email]
>>>>> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>>>>>
>>>>
>>>
>>
>
>
> _______________________________________________
> Libraries mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
>


_______________________________________________
Libraries mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
12