Synchronous exceptions

Generated as a result of a failing action in IO (same thread). Usually thrown using throwIO.

Impure exceptions

Thrown in pure code by partial function. Ideally, we would not use such functions, a better practice is to return an Either type in this situation.

Asynchronous exceptions

Can occur anywhere, including in pure code. Generated when another thread or the runtime system is trying to kill the current thread (via throwTo) or report an “unrecoverable” situation like a StackOverflow.

Interruptible actions

Some operations are interruptible by async exceptions even within a mask. This is the case for blocking functions such as takeMVar but also for most I/O operations dealing with the outside world.


throwIO :: Exception e => e -> IO a (1)
1 you should always prefer throwIO to throw
try :: Exception e => IO a -> IO (Either e a)

catch  :: Exception e
        => IO a        -- ^ computation
        -> (e -> IO a) -- ^ handler
        -> IO a
  • catch has an implicit mask around the handler.

  • try does not have a similar default. Don’t use it for recovering from an async exception.

        :: IO a -- ^ computation
        -> IO b -- ^ computation to run afterward even if an exception was raised
        -> IO a
a `finally` sequel =
  mask $ \restore -> do
    r <- restore a `onException` sequel
    _ <- sequel
    return r

-- | Like 'finally', but only performs the final action if there was an
-- exception raised by the computation.
onException :: IO a -> IO b -> IO a
onException io what =
  io `catch` \e -> do _ <- what
                        throwIO (e :: SomeException)
        :: IO a        -- ^ acquire resource
        -> (a -> IO b) -- ^ release resource
        -> (a -> IO c) -- ^ use resource
        -> IO c
bracket before after use =
  mask $ \restore -> do
    a <- before
    r <- restore (use a) `onException` after a
    _ <- after a
    return r

Monad primitives

The exceptions package defines Control.Monad.Catch with

class Monad m => MonadThrow m where
  throwM :: Exception e => e -> m a
class MonadThrow m => MonadCatch m where
  catch :: Exception e => m a -> (e -> m a) -> m a
class MonadCatch m => MonadMask m where
  mask :: ((forall a. m a -> m a) -> m b) -> m b
  uninterruptibleMask :: ((forall a. m a -> m a) -> m b) -> m b
  • Instances should ensure that, in the following code f ‘finally’ g, the action g is called regardless of what occurs within f, including async exceptions.

  • ExceptT is not an instance of MonadMask. See MonadMask vs MonadBracket