This proposal attempts to summarise the interface design of the exception annotation scheme proposed in GHC Proposal #330. Specifically, this proposal covers the changes described in sections 2.1, 2.3, 2.4, and 2.5.
Note that the GHC Proposal is free-standing; no reading of the discussion which lead to its current accepted state should be necessary to understand its contents. Consequently, to avoid repetition I will refer back to the GHC Proposal instead of repeating myself here. I will, however, attempt to give some color to the interfaces by providing typical usage examples where necessary. However, the GHC Proposal is to be considered the canonical definition of the interfaces; in particular, section 2 and its subsections precisely captures the changes proposed in base.
Goal
The goal of this work is to allow users to more easily locate the causes of exceptions within their program. In particular, we note that when troubleshooting the context in which an exception occurred can be just as important in identifying the cause as the particulars of the event itself. For this reason, "cause" here may mean many things:
Which function did the exception arise from?
What did the execution-stack look like when the exception arose?
Which profiling cost-centres did the exception arise under?
In the case of a server application, what request triggered the exception?
Was the exception thrown while handling another exception?
Since the particular information necessary to determine the "cause" of an exception can be quite domain-dependent, we propose a general-purpose annotation mechanism following the model of the annotated-exception library. This mechanism allows the user to attach dynamically-typed annotations to exceptions, which can be later inspected and displayed.
With this general-purpose mechanism in hand, we define a set of annotation types for capturing various backtrace flavours (namely, HasCallStack stacks, profiling cost-centre-stacks, DWARF execution stacks, and native Haskell execution stacks).
Exception annotations
The proposed annotation mechanism is introduced by extending the existing SomeException type, which represents the root of the exception hierarchy (readers wanting a refresher for GHC's exception system are referred to "An Extensible Dynamically-Typed Hierarchy of Exceptions"). This type is currently defined as:
-- Currently in Control.Exception
data SomeException = forall e. (Exception e) => SomeException e
We propose (section 2.4) to add an additional implicit parameter context to the SomeException data constructor, following the model of HasCallStack:
-- In Control.Exception
data SomeException = forall e. (Exception e, HasExceptionContext) => SomeException e
type HasExceptionContext = (?exceptionContext :: ExceptionContext)
someExceptionContext :: SomeException -> ExceptionContext
Since this addition would represent a breaking change, we propose (section 8.6) to provide defaulting logic in the typechecker, again following the model of HasTypeStack.
As described in section 2.3, ExceptionContext is an abstract, order-preserving collection of annotations. As a concrete representation we propose to use a simple List:
Exception annotations are dynamically-typed values distinguished by the ExceptionAnnotation typeclass (section 2.1):
-- In Control.Exception.Annotation
data SomeExceptionAnnotation where
SomeExceptionAnnotation ::
forall a. (ExceptionAnnotation a) => a -> SomeExceptionAnnotation
Where the ExceptionAnnotation class provides for rendering of annotations to a user-facing string (following the model of displayException; section 2.1):
-- In Control.Exception.Annotation
class (Typeable a) => ExceptionAnnotation a where
displayExceptionAnnotation :: a -> String
default displayExceptionAnnotation :: Show a => a -> String
displayExceptionAnnotation = show
We provide a few combinators for manipulating exception context (section 2.3):
-- In Control.Exception.Context
emptyExceptionContext :: ExceptionContext
addExceptionAnnotation :: ExceptionAnnotation a => a -> ExceptionContext -> ExceptionContext
getExceptionAnnotations :: ExceptionAnnotation a => ExceptionContext -> [a]
getAllExceptionAnnotations :: ExceptionContext -> [SomeExceptionAnnotation]
Adding annotations to exceptions
Attaching annotations to an exception may be accomplished using an introduced addExceptionContext function:
-- In Control.Exception
addExceptionContext :: (ExceptionAnnotation a)
=> a -> SomeException -> SomeException
It is anticipated that users may frequently want to run an IO action, ensuring that any exceptions thrown therein are given an annotation. This can be accomplished using the annotateIO function (section 2.4):
-- In Control.Exception
annotateIO :: ExceptionAnnotation a => a -> IO r -> IO r
Showing annotations
Naturally displayExceptionAnnotation can be used to display each of the annotations contained within an ExceptionContext (section 2.3):
-- In Control.Exception.Context
displayExceptionContext :: ExceptionContext -> String
displayExceptionContext (ExceptionContext anns0) = go anns0
where
go (SomeExceptionAnnotation ann : anns) = displayExceptionAnnotation ann ++ "\n" ++ go anns
go [] = "\n"
To make annotations visible to users, we propose to:
Use displayException in GHC's top-level exception handler (CLC#198)
Teach the displayException implementation of SomeException to display attached ExceptionContext via displayExceptionContext (section 2.10)
Providing exception context to handlers
Typically when users use handle or catch they do so against a concrete exception type, not SomeException. Since this pattern would prevent access to ExceptionContext, we propose adding a convenient wrapper to expose an arbitrary exception with its context. The Exception instance of this wrapper allows it to be used in both throwing and catching contexts:
-- In Control.Exception
data ExceptionWithContext a = ExceptionWithContext ExceptionContext a
instance Show a => Show (ExceptionWithContext a)
instance Exception a => Exception (ExceptionWithContext a) where
toException (ExceptionWithContext ctxt e) = SomeException e
where ?exceptionContext = ctxt
fromException se = do
e <- fromException se
return (ExceptionWithContext (someExceptionContext se) e)
displayException = displayException . toException
Usage
In the case of a server application, the author may wish to augment exceptions thrown within a request handler with information about the request being handled. This can be achieved with the above interfaces as follows:
data Request
instance ExceptionAnnotation Request where
displayExceptionAnnotation req =
"While handling request " ++ show req
handler :: Request -> IO ()
handler req = annotateIO req $ do ...
This example is representative of what we believe will be typically needed by end-users.
Far fewer users (e.g. structured and application-specific logging infrastructure) will need to directly inspect exceptions' context.
Migration
Thanks to the default behavior described in Section 8.6, the HasExceptionContext constraints introduced in SomeException should be discharged automatically.
All other changes are strict additions which should require no migration effort on the part of library authors or application developers.
Tracking ticket: #164
This proposal attempts to summarise the interface design of the exception annotation scheme proposed in GHC Proposal #330. Specifically, this proposal covers the changes described in sections 2.1, 2.3, 2.4, and 2.5.
Note that the GHC Proposal is free-standing; no reading of the discussion which lead to its current accepted state should be necessary to understand its contents. Consequently, to avoid repetition I will refer back to the GHC Proposal instead of repeating myself here. I will, however, attempt to give some color to the interfaces by providing typical usage examples where necessary. However, the GHC Proposal is to be considered the canonical definition of the interfaces; in particular, section 2 and its subsections precisely captures the changes proposed in
base
.Goal
The goal of this work is to allow users to more easily locate the causes of exceptions within their program. In particular, we note that when troubleshooting the context in which an exception occurred can be just as important in identifying the cause as the particulars of the event itself. For this reason, "cause" here may mean many things:
Since the particular information necessary to determine the "cause" of an exception can be quite domain-dependent, we propose a general-purpose annotation mechanism following the model of the
annotated-exception
library. This mechanism allows the user to attach dynamically-typed annotations to exceptions, which can be later inspected and displayed.With this general-purpose mechanism in hand, we define a set of annotation types for capturing various backtrace flavours (namely,
HasCallStack
stacks, profiling cost-centre-stacks, DWARF execution stacks, and native Haskell execution stacks).Exception annotations
The proposed annotation mechanism is introduced by extending the existing
SomeException
type, which represents the root of the exception hierarchy (readers wanting a refresher for GHC's exception system are referred to "An Extensible Dynamically-Typed Hierarchy of Exceptions"). This type is currently defined as:We propose (section 2.4) to add an additional implicit parameter context to the
SomeException
data constructor, following the model ofHasCallStack
:Since this addition would represent a breaking change, we propose (section 8.6) to provide defaulting logic in the typechecker, again following the model of
HasTypeStack
.As described in section 2.3,
ExceptionContext
is an abstract, order-preserving collection of annotations. As a concrete representation we propose to use a simpleList
:Exception annotations are dynamically-typed values distinguished by the
ExceptionAnnotation
typeclass (section 2.1):Where the
ExceptionAnnotation
class provides for rendering of annotations to a user-facing string (following the model ofdisplayException
; section 2.1):We provide a few combinators for manipulating exception context (section 2.3):
Adding annotations to exceptions
Attaching annotations to an exception may be accomplished using an introduced
addExceptionContext
function:It is anticipated that users may frequently want to run an
IO
action, ensuring that any exceptions thrown therein are given an annotation. This can be accomplished using theannotateIO
function (section 2.4):Showing annotations
Naturally
displayExceptionAnnotation
can be used to display each of the annotations contained within anExceptionContext
(section 2.3):To make annotations visible to users, we propose to:
displayException
in GHC's top-level exception handler (CLC#198)displayException
implementation ofSomeException
to display attachedExceptionContext
viadisplayExceptionContext
(section 2.10)Providing exception context to handlers
Typically when users use
handle
orcatch
they do so against a concrete exception type, notSomeException
. Since this pattern would prevent access toExceptionContext
, we propose adding a convenient wrapper to expose an arbitrary exception with its context. TheException
instance of this wrapper allows it to be used in both throwing and catching contexts:Usage
In the case of a server application, the author may wish to augment exceptions thrown within a request handler with information about the request being handled. This can be achieved with the above interfaces as follows:
This example is representative of what we believe will be typically needed by end-users.
Far fewer users (e.g. structured and application-specific logging infrastructure) will need to directly inspect exceptions' context.
Migration
Thanks to the default behavior described in Section 8.6, the
HasExceptionContext
constraints introduced inSomeException
should be discharged automatically.All other changes are strict additions which should require no migration effort on the part of library authors or application developers.