BSD-3-Clause licensed by Matt Parsons
Maintained by [email protected]
This version can be pinned in stack with:annotated-exception-,1785

Module documentation for


This library provides a special AnnotatedException type which allows you to decorate Haskell exceptions with additional information. This decoration is totally transparent, and even works with exceptions thrown outside of your application code.

To provide an annotation, you’d use the function checkpoint. This will attach the provided value to any exception that bubbles up through it.

import Control.Exception.Annotated

data MyException = MyException
    deriving (Show, Exception)

main :: IO ()
main = do
    checkpoint "Foo" $ do
        throw MyException

When this program crashes, it will crash with an AnnotatedException that contains the annotation "Foo".

λ> checkpoint "Foo" $ throw MyException
*** Exception: AnnotatedException {annotations = ["Foo"], exception = MyException}

These annotations survive, even if you catch and rethrow with a different exception.

data OtherException = OtherException
    deriving (Show, Exception)

woah :: IO ()
woah = do
        checkpointed =
            checkpoint "Foo" (throw MyException)
        handler MyException =
            throw OtherException


Notice how the checkpoint call doesn’t cover the throw OtherException - the exception [Annotation] lives on the thrown exception itself, and this library’s catch function ensures that we don’t lose that context.

λ> (checkpoint "Foo" (throw MyException)) `catch` \MyException -> throw OtherException
*** Exception: AnnotatedException {annotations = ["Foo"], exception = OtherException}

You can also attach a CallStack to any exception using throwWithCallStack.

Now, you’re about to report your exceptions, up near main. We can use try in this module to always get the annotations.

main = do
    eresult <- try $ myProgram
    case eresult of
        Left (AnnotatedException annotations exception) ->
            reportException annotations exception
        Right a ->
            pure a


Changelog for annotated-exception

  • #27
    • Ensure that flatten combines CallStacks even when the callstack is attached manually.

  • #18
    • Add checkpointCallStack even when catch doesn’t catch an AnnotatedException

  • #17
    • Add HasCallStack to catch and catches

  • #14
    • Define Control.Exception.Annotated.UnliftIO.checkpointCallStack without re-exporting the MonadCatch variant. Sigh.

  • #13
    • Fixed a bug in UnliftIO.catches where it would infinitely recurse.

  • #12
    • Removed the Eq instance for Annotation as well as the Eq constraint in AnnC. These instances were only used for testing, and prevented the natural use of CallStack in a [Annotation].
    • Removed the Eq instance for AnnotatedException as a consequence of dropping the Eq instance on Annotation.
    • Removed the new function. Use pure or exceptionWithCallStack instead.
    • Fixed a double-annotation bug in checkpointCallStackWith.
    • checkpointCallStack appends to the call-site list.
    • Pretty much everything now merges the CallStacks together. throw includes a CallStack, as do checkpoint and checkpointMany.

  • #8
    • There was a bug where catching or trying to catch an exception of the wrong type would trigger an infinite loop as the fromException method kept digging and digging and would be unable to make things work out. The fromException code no longer tries to flatten out these exceptions. However, toException does flatten it, so all tests still pass.

  • #6
    • Add Control.Exception.Annotated.UnliftIO that uses MonadUnliftIO instead of MonadCatch and MonadThrow.
    • Actually expose catches

  • #4
    • Add catches
    • Replace Control.Exception.Safe.try with try that can get an AnnotatedException e or a regular, un-Annotated e.

  • Initial Release