John Wiegley and I are currently working- together with some members of the community- on various exception handling related code. As part of this work, we're also going to be publishing a number of tutorials on this topic. Eventually, we'll collect all of this information together into a more cohesive whole, but for now we wanted to get the content out there to the community as we produce it.
---
Love them or hate them, runtime exceptions are a reality of writing Haskell code. Consider the following fake code:
```haskell
myFunc = do
resource <- acquireScarceResource
useResource resource
releaseResource resource
```
In a world without exceptions, this seems completely reasonable. However, what happens if the call to `useResource` throws a runtime exception? In such a case, `releaseResource` would never be called, and our scarce resource would never be released. In concurrent code dealing with mutexes, such a bug could lead to a deadlock.
The solution to this problem is well known: use the bracket function. Then our above example reduces to `myFunc = bracket acquireScarceResource releaseResource useResource`. We're now safe from any exception thrown by `useResource`, and even from asynchronous exceptions (a topic I'll try to avoid in this post).
But let's analyze the type signature of `bracket`:
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
How do we use such a function in monad transformer stack, where the functions we pass in won't live in the `IO` monad itself?
Note that for the rest of this tutorial, I'm going to talk about the finally function instead of `bracket`, which is really just a simplified version of the latter. The reasoning used will apply to not only `bracket`, but also to many other functions in Control.Exception. Its signature is `IO a -> IO b -> IO a`, and its purpose is to ensure that the second function is called regardless of what exceptions are thrown by the first (or any asynchronous exceptions... sorry for bringing those up again).
## The simple case: the ReaderT transformer
Let's forget about any kind of grand solution. Instead, let's take one of the simplest monad transformers that exists, `ReaderT`, and see how we can write a `finally` that works with it. Since a `ReaderT` simply passes in some environment to all actions, this turns out to be really easy:
```haskell active
import Control.Monad.Reader
import Control.Exception (finally, try, ErrorCall)
finallyReaderT :: ReaderT r IO a -> ReaderT r IO b -> ReaderT r IO a
finallyReaderT a sequel = do
r <- ask
liftIO $ runReaderT a r `finally` runReaderT sequel r
main :: IO ()
main = do
res <- try $ runReaderT
(step1 `finallyReaderT` step2)
"This is the environment"
print (res :: Either ErrorCall ())
where
step1 = do
r <- ask
liftIO $ putStrLn $ "In step1, r == " ++ show r
error "Erroring out, bye!"
step2 = do
r <- ask
liftIO $ putStrLn $ "In step2, r == " ++ show r
```
That turns out to be pretty simple. And by reusing the existing `finally` function, we can feel comfortable knowing that our implementation is correct (or at least as correct as `finally` itself).
## First complication: mutable state
But `ReaderT` is a really simple case to support, since it's just a read-only environment. Let's up the ante a bit, and try using `StateT` instead. We'll now need to deal with threading the mutable state variable through. Before jumping into the code, let's consider abstractly how would could achieve this threading. We have two possible cases: an exception is thrown before calling the cleanup function, or an exception is not thrown. If no exception is thrown, then we can extract the state value from the result of the first function call, pass that to the cleanup function, and then return the final state value as the new mutable state.
But if an exception is thrown, there won't be a chance to extract the updated state value from the first function. In that case, we'll only have the initial state value available to pass through to the cleanup function. (The new result state is irrelevant, since the exception is going to be rethrown anyway.) As described until now, let's see what this kind of `finally` implementation may look like.
```haskell active
import Prelude hiding (catch)
import Control.Monad.State
import Control.Exception
finallyStateT :: StateT s IO a -> StateT s IO b -> StateT s IO a
finallyStateT a sequel = do
s1 <- get
(result, s2) <- liftIO $ runStateT a s1 `catch` \e -> do
_ignored <- runStateT sequel s1
throwIO (e :: SomeException)
(_ignored, s3) <- liftIO $ runStateT sequel s2
put s3
return result
main :: IO ()
main = do
res <- try $ runStateT
(step1 `finallyStateT` step2)
1
print (res :: Either ErrorCall ((), Int))
where
step1 = do
s <- get
liftIO $ putStrLn $ "In step1, s == " ++ show s
put $ s + 1
error "Erroring out, bye!" -- Try commenting me out
step2 = do
s <- get
liftIO $ putStrLn $ "In step2, s == " ++ show s
put $ s + 1
```
This seems to work, but there are a two problems:
* We were not able to reuse the standard `finally` function to implement this, since it wouldn't allow us to manually thread state. Practically, that means our implementation is susceptible to asynchronous exceptions (argh... those came up again).
* The behavior is pretty inconsistent. In one case, we use the original mutable state value and ignore the updated state from the cleanup function. In the other, we use the updated state value from the first function and keep the updated mutable state.
So let's instead try out a different approach. Regardless of whether an exception is thrown or not, we'll call the cleanup function using the original mutable state, and always ignore the state produced by the cleanup function.
```haskell active
import Control.Monad.State
import Control.Exception
finallyStateT :: StateT s IO a -> StateT s IO b -> StateT s IO a
finallyStateT a sequel = do
s1 <- get
(result, s2) <- liftIO $ runStateT a s1 `finally`
runStateT sequel s1
put s2
return result
main :: IO ()
main = do
res <- try $ runStateT
(step1 `finallyStateT` step2)
1
print (res :: Either ErrorCall ((), Int))
where
step1 = do
s <- get
liftIO $ putStrLn $ "In step1, s == " ++ show s
put $ s + 1
error "Erroring out, bye!" -- Try commenting me out
step2 = do
s <- get
liftIO $ putStrLn $ "In step2, s == " ++ show s
put $ s + 1
```
This solves my two complaints from before, but unfortunately introduces a new one: the behavior is not really intuitive in the non-exception case. Nonetheless, in my opinion, this is the right way to implement things due to the consistency. We'll cover a method to get back the more intuitive semantics towards the end of this post.
## Second complication: alternative exit paths
The previous section essentially provided two approaches to writing a transformer-based `finally`: base it off of primitives like `catch`, or reuse `finally`. It turns out that both of these approaches have quite a bit of prior art. In the first case, there are the `MonadCatchIO-mtl` and `MonadCatchIO-transformers` packages, as well as the more recent `exceptions` package. These all define a typeclass for the primitive operations of `catch`ing and masking asynchronous exceptions (we just can't escape those, can we?).
Let's extract the `catch` logic from our first `StateT` example above to demonstrate the technique:
```haskell active
import Prelude hiding (catch)
import Control.Monad.State
import Control.Exception
-- show
catchStateT :: Exception e
=> StateT s IO a
-> (e -> StateT s IO a)
-> StateT s IO a
catchStateT a onE = do
s1 <- get
(result, s2) <- liftIO $ runStateT a s1 `catch` \e ->
runStateT (onE e) s1
put s2
return result
finallyStateT :: StateT s IO a -> StateT s IO b -> StateT s IO a
finallyStateT a sequel = do
result <- a `catchStateT` \e -> do
_ignored <- sequel
liftIO $ throwIO (e :: SomeException)
_ignored <- sequel
return result
-- /show
main :: IO ()
main = do
res <- try $ runStateT
(step1 `finallyStateT` step2)
1
print (res :: Either ErrorCall ((), Int))
where
step1 = do
s <- get
liftIO $ putStrLn $ "In step1, s == " ++ show s
put $ s + 1
error "Erroring out, bye!" -- Try commenting me out
step2 = do
s <- get
liftIO $ putStrLn $ "In step2, s == " ++ show s
put $ s + 1
```
That's certainly a bit prettier than what we had before (though it's still not async-exception safe). And what's really nice is that this definition of `finallyStateT` doesn't actually have anything `StateT`-specific about it. So presuming we had a `catch` function for some other transformer, we could reuse the same definition. Let's try this out for `ErrorT`.
```haskell active
import Prelude hiding (catch)
import Control.Monad.Error
import Control.Exception
catchErrorT :: (Exception e, Error s)
=> ErrorT s IO a
-> (e -> ErrorT s IO a)
-> ErrorT s IO a
catchErrorT a onE = do
eresult <- liftIO $ runErrorT a `catch` \e ->
runErrorT (onE e)
either throwError return eresult
finallyErrorT :: Error s => ErrorT s IO a -> ErrorT s IO b -> ErrorT s IO a
finallyErrorT a sequel = do
result <- a `catchErrorT` \e -> do
_ignored <- sequel
liftIO $ throwIO (e :: SomeException)
_ignored <- sequel
return result
main :: IO ()
main = do
res <- try $ runErrorT
(step1 `finallyErrorT` step2)
print (res :: Either ErrorCall (Either String ()))
where
step1 = do
liftIO $ putStrLn $ "In step1"
throwError "This should be interesting"
step2 = liftIO $ putStrLn $ "In step2"
```
Go ahead and run that code snippet. Do you notice something not there to be noticed? That's right, the cleanup function is never called! Here we have a function called `finally` which, despite its name, doesn't actually call the cleanup function when it finally exits. What gives?
The issue is that, with an `ErrorT s IO` stack, there are __three__ ways to exit the function: normally, with a runtime exception, or with a `throwError` result. In `finallyErrorT`, the call to `catchErrorT` accounts for the runtime exception case, and the following call to `sequel` accounts for the normal exit case. But there's no way to account for the `throwError` case without adding `ErrorT`-specific logic to `finally` (we'll do that in a moment).
You can try this example out with `MonadCatchIO-mtl` or `MonadCatchIO-transformers` and reproduce this buggy behavior. This affects both the `ErrorT` and `ContT` transformers. In the `exceptions` package, this also applies to the `CatchT` type. (John Wiegley discussed this issue with Edward, who described this as a documentation bug, since `CatchT` should not be used on top of `IO`.)
Doing this correctly is certainly possible, as you can see with the following example:
```haskell active
import Control.Monad.Error
import Control.Exception
-- show
finallyErrorT :: Error s => ErrorT s IO a -> ErrorT s IO b -> ErrorT s IO a
finallyErrorT a sequel = do
eresult <- liftIO $ runErrorT a `finally` runErrorT sequel
either throwError return eresult
-- /show
main :: IO ()
main = do
res <- try $ runErrorT
(step1 `finallyErrorT` step2)
print (res :: Either ErrorCall (Either String ()))
where
step1 = do
liftIO $ putStrLn $ "In step1"
throwError "This should be interesting"
step2 = liftIO $ putStrLn $ "In step2"
```
But this seems to imply that we would need to write a separate implementation of `finally` for each and every transformer, which would be tedious and error-prone. There must be a better way.
## monad-control
If you look at our various implementations so far, they all follow a similar pattern: embedding the state of the monad inside the value, running the underlying monadic action, and then rebuilding the monadic state from the result. This procedure can be done with many different transformers (basically, all common transformers except `ContT`). To capture this concept, we have the `MonadTransControl` and `MonadBaseControl` typeclasses in the [`monad-control`](https://www.fpcomplete.com/haddocks/monad-control) package.
`monad-control` is frankly pretty complicated, and I'm not going to delve into all of its details here. Instead, I'll point you to the [`lifted-base`](https://www.fpcomplete.com/haddocks/lifted-base) package, which uses `monad-control` to create transformer-friendly versions of many common functions in `base`. As you'd expect based on this tutorial, exception handling functions are present, but also concurrency, timeouts, and mutable variables (which will become important in just a moment).
`monad-control` is- as far as I know- the third attempt at a library to cover these concepts. Previous solutions were `monad-peel` and my own [MonadInvertIO](http://hackage.haskell.org/packages/archive/neither/0.1.0/doc/html/Control-Monad-Invert.html), part of earlier versions of `neither`. While in my opinion these other two approaches are easier to understand, `monad-control` is highly optimized, and therefore gets my recommendation as the go-to library for handling monad transformer stacks.
## More intuitive mutable state
We're left with one annoyance with the `monad-control`-based solution: we lose any mutations performed to our mutable state either before an exception is thrown, or in our cleanup functions. Is there some way to keep the elegance of `monad-control` but get back the more intuitive behavior? The answer is yes, but you may not like it.
When dealing with either `StateT` or `WriterT` transformers, the behavior which I would consider most intuitive would be that any mutations are immediately captured. Unfortunately, this can't be achieved at all using the standard `StateT` and `WriterT` implementations, since the monadic state is thrown away as soon as an exception is thrown.
In this case, my solution is to use a mutable variable- such as an `IORef`- and keep a reference to it in a `ReaderT`. As an example, this technique is used by the HandlerT transformer used in Yesod.
To demonstrate the concept, here's an implementation of the RWST monad transformer which uses `IORef`s for holding mutable state. For completeness, I'll include appropriate instances for `monad-control` and demonstrate usage of `lifted-base`.
```haskell active
{-# OPTIONS_GHC -Wall -Werror #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
import Control.Monad.RWS.Class
import Control.Monad.Reader
import Control.Monad.Base
import Control.Applicative (Applicative)
import Data.IORef.Lifted
import Data.Monoid (Monoid, (<>), mempty)
import Control.Exception.Lifted
import Control.Monad.Trans.Control
data Env r w s = Env
{ envReader :: !r
, envWriter :: !(IORef w)
, envState :: !(IORef s)
}
newtype RWST r w s m a = RWST (ReaderT (Env r w s) m a)
deriving (Functor, Applicative, Monad, MonadIO, MonadTrans)
instance Monad m => MonadReader r (RWST r w s m) where
ask = RWST $ liftM envReader ask
local f (RWST g) =
RWST $ local f' g
where
f' env = env { envReader = f $ envReader env }
instance (MonadBase IO m, Monoid w) => MonadWriter w (RWST r w s m) where
tell w = RWST $ ask >>= flip modifyIORef (<> w) . envWriter
listen (RWST (ReaderT f)) = RWST $ ReaderT $ \env -> do
iwriter <- newIORef mempty
result <- f env { envWriter = iwriter }
w <- readIORef iwriter
return (result, w)
pass (RWST (ReaderT f)) = RWST $ ReaderT $ \env -> do
(result, g) <- f env
modifyIORef (envWriter env) g
return result
instance MonadBase IO m => MonadState s (RWST r w s m) where
get = RWST $ ask >>= readIORef . envState
put s = RWST $ ask >>= flip writeIORef s . envState
instance (MonadBase IO m, Monoid w) => MonadRWS r w s (RWST r w s m)
runRWST :: (MonadBase IO m, Monoid w) => RWST r w s m a -> r -> s -> m (a, s, w)
runRWST (RWST (ReaderT f)) r s = do
iwriter <- newIORef mempty
istate <- newIORef s
a <- f $ Env r iwriter istate
w <- readIORef iwriter
s' <- readIORef istate
return (a, s', w)
instance MonadBase b m => MonadBase b (RWST r w s m) where
liftBase = lift . liftBase
instance MonadTransControl (RWST r w s) where
newtype StT (RWST r w s) a = StRWS {unStRWS :: a}
liftWith f = RWST $ ReaderT $ \r -> f $ \(RWST t) -> liftM StRWS $ runReaderT t r
restoreT = RWST . ReaderT . const . liftM unStRWS
instance MonadBaseControl b m => MonadBaseControl b (RWST r w s m) where
newtype StM (RWST r w s m) a = ST { unST :: ComposeSt (RWST r w s) m a }
liftBaseWith = defaultLiftBaseWith ST
restoreM = defaultRestoreM unST
main :: IO ()
main = do
res <- try $ runRWST
(step1 `finally` step2)
"This is the environment"
0
print (res :: Either ErrorCall ((), Int, [Bool]))
where
step1 = do
liftIO $ putStrLn $ "In step1"
modify (+ 1)
error "User exception"
step2 = do
s <- get
liftIO $ putStrLn $ "In step2: " ++ show s
```