One of the main deficiencies of `transformers`

is that the `MonadTrans`

class does not let you lift functions like `catchError`

. The `mtl`

library provides one solution to this problem, which is to type-class `catchError`

and `throwError`

using the `MonadError`

type class.

That's not to say that `transformers`

has no solution for lifting `catchError`

and `throwError`

; it's just really verbose. Each module provides a `liftCatch`

function that you use to lift a `catchError`

function from the base monad to the transformed monad.

For example, `Control.Monad.Trans.State`

provides the following `liftCatch`

function:

```
Control.Monad.Trans.State.liftCatch
:: (m (a, s) -> (e -> m (a, s)) -> m (a, s))
-> StateT s m a -> (e -> StateT s m a) -> StateT s m a
```

If your monad transformer stack was:

`myStack :: (Monad m, Error e) => StateT s (ErrorT e m)`

... then you would apply `liftCatch`

directly to `catchError`

to get a catch function that worked at the `StateT`

layer:

```
Control.Monad.Trans.State.liftCatch catchError
:: (Monad m, Error e)
=> StateT s (ErrorT e m) a
-> (e -> StateT s (ErrorT e m) a)
-> StateT s (ErrorT e m) a
```

But what if you had a `WriterT`

layer in between like this:

`myStack :: (Monad m) => StateT s (WriterT w (ErrorT e m))`

You'd have to use `liftCatch`

from the `Control.Monad.Trans.Writer`

module to further lift the `catchError`

to work with this stack:

```
import Control.Monad.Trans.State as S
import Control.Monad.Trans.Writer as W
S.liftCatch (W.liftCatch catchError)
:: (Monad m, Error e)
=> StateT s (WriterT w (ErrorT e m)) a
-> (e -> StateT s (WriterT w (ErrorT e m)) a)
-> StateT s (WriterT w (ErrorT e m)) a
```

The advantage of this solution is that type inference works extraordinarily well and it is Haskell98. The disadvantage of this solution is that it is very verbose.

So I will propose a less verbose solution that has a syntax resembling the `lens`

library, although there will be no true lenses involved (I think). Define the following `catching`

function:

```
catching
:: (Monad m, Error e)
=> ((ErrorT e m a -> (e -> ErrorT e m a) -> ErrorT e m a) -> r)
-> r
catching k = k catchError
```

The `k`

in the above function will be a series of composed `liftCatch`

functions that we will apply to `catchError`

. However, in the spirit of the `lens`

library we will rename these `liftCatch`

functions to be less verbose and more hip and sexy:

```
import qualified Control.Monad.Trans.Maybe as M
import qualified Control.Monad.Trans.Reader as R
import qualified Control.Monad.Trans.State as S
import qualified Control.Monad.Trans.Writer as W
import qualified Pipes.Lift as P
state = S.liftCatch
writer = W.liftCatch
reader = R.liftCatch
maybe = M.liftCatch
pipe = P.liftCatchError
```

Now let's say that we have a monad transformer stack of type:

```
myStack
:: (Monad m, Error e)
=> StateT s (MaybeT (ErrorT e m)) r
```

If I want to catch something at the outer `StateT`

level, I would just write:

```
myStack = catching (state.maybe) action $ \e -> do
-- The handler if `action` fails
...
```

If I add more layers to my monad transformer stack, all I have to do is point deeper to the stack by composing more references:

```
myStack
:: (Monad m, Error e, Monoid w)
=> Pipe a b (StateT s (WriterT w (MaybeT m))) r
myStack = catching (pipe.state.writer.maybe) action $ \e -> ...
```

Also, these references are first-class Haskell values, so you can bundle them and manipulate them with your favorite functional tools:

```
myStack = do
let total = pipe.state.writer.maybe
catching total action1 $ \e -> ...
catching total action2 $ \e -> ...
```

This approach has a few advantages over the traditional `MonadError`

approach:

- You get improved type inference
- You get type errors earlier in development. With
`MonadError`

the compiler will not detect an error until you try to run your monad transformer stack. - You get better type errors.
`MonadError`

errors will arise at a distance where you call`runErrorT`

even though the logical error is probably at the site of the`catchError`

function. - Functional references are first class and type classes are not

However, we don't want to lift just `catchError`

. There are many other functions that `transformers`

can lift such as `local`

, `listen`

, and `callCC`

. An interesting question would be whether this is some elegant abstraction that packages all these lifting operations into a simple type in the same way that lenses package getters, setters, traversals, maps, and zooms into a single abstraction. If there were, then we could reuse the same references for catching, listening, and other operations that are otherwise difficult to lift:

```
listening (state.error) m
passing (maybe.writer) m
catching (writer.maybe) m $ \e -> ...
```

I have no idea if such an elegant abstraction exists, though, which is why I'm writing this post to solicit ideas.

## No comments:

## Post a Comment