In their paper “Extensible Effects”, Kiselyov, Sabry and Swords (KSS) introduce an effect system for Haskell that is an alternative to the classical monad transformers approach. There is a Haskell library of the same name implementing the ideas of the paper. Dan Doel noted in a recent article that this is one of many papers which investigate effects and their associated handlers, in large part aiming to move from what is seen to be the “static” approach of monad transformers, to the supposedly more “dynamic” approach of algebraic effects.

Dan’s article is a defence of the classical monad transformers approach, and he argues convincingly “that the staticness [of monad transformers] can actually be a useful property, that certain ways of harnessing it can make the confusing constructions less so, and … that algebraic effects perhaps aren’t so different from what we are already using”.

KSS give two examples where they claim their approach is superior to
monad transformers. The first is that it is awkward to combine
exceptions with non-determinism. However, Dan implements a simple
alternative to `catchError`

and ensures that the exception handler
removes from the set of effects. In this way he efficiently disposes
with the supposed awkwardess. The second objection of KSS is that a
certain interleaving of a read effect with coroutines is *impossible*
to achieve with monad transformers. In this article I will
demonstrate that contrary to KSS’s assertion, this interleaving *is*
possible with monad transformers, and moreover straightforward.

`catchError`

from the mtl
runs a monad transformer stack that contains an “exception raising”
layer and handles any exceptions with a handler that can potentially
rethrow them. The definition has the following signature

`catchError :: MonadError e m => m a -> (e -> m a) -> m a`

The monad `m`

is the same in the argument and return value, thus any
exceptions that are rethrown are rethrown at exactly the same level of
the stack. As KSS note, this *does* result in a particular
inflexibility when mixing effects.

However, Dan realised that handlers should *remove* from the set of
effects. That is, the monad of the return value should be “smaller”
than the monad of the argument. He defines
`localCatch`

with the signature

`localCatch :: Monad m => ErrorT e m a -> (e -> m a) -> m a`

which handles exceptions at the outermost level and gives the user the chance to rethrow them at a lower level. Using this formulation yields the results that KSS are seeking with no further difficulties.

KSS provide the following type `CoT`

which is a co-routine transformer

```
type CoT a m = ContT (Y m a) m
data Y m a = Done | Y a (() -> m (Y m a))
```

and define a function which mixes read effects with coroutines. The
idea is to use `local`

to modify the local state of a read operation.
`local`

has the signature

`MonadReader r m => (r -> r) -> m a -> m a`

and the function is

```
th3 :: MonadReader Int m => CoT Int m ()
th3 = ay >> ay >> local (+10) (ay >> ay)
where ay = ask >>= yield
```

They demonstrate that this has unexpected behaviour. They also consider the opposite order of effects

```
th4 :: Monad m => ReaderT Int (CoT Int m) ()
th4 = ay >> ay >> local (+10) (ay >> ay)
where ay = ask >>= lift . yield
```

but this does not work either. However, what I learned from Dan’s
article is that handlers should *remove* from the set of effects, so
instead of `local`

we can try implementing `localLocal`

(awkwardly
named to be consistent with Dan’s terminology!) which removes the read
effect.

```
localLocal :: MonadReader a m => (a -> r) -> ReaderT r m b -> m b
localLocal f m = ask >>= runReaderT m . f
```

Using `localLocal`

highlights the problem. The `ay`

outside the
`localLocal`

needs to have a different type than the `ay`

*inside* the
`localLocal`

.

```
th3_explicit :: Monad m => CoT Int (ReaderT Int m) ()
th3_explicit = ay1 >> ay1 >> localLocal (+10) (ay2 >> ay2)
where ay1 :: Monad m => CoT Int (ReaderT Int m) ()
ay1 = lift ask >>= yield
ay2 :: Monad m => ReaderT Int (CoT Int m) ()
ay2 = ask >>= lift . yield
```

This implementation gives exactly the results that KSS are looking
for. Furthermore it’s not hard to remove the duplication by defining
a monad transformer class `MonadCo`

which abstracts over continuation
monads and provides a `yieldG`

operation (a transformer-polymorphic
version of `yield`

). This leads to a neat and general implementation

```
th3_MTL :: (MonadCo Int m, MonadReader Int m) => m ()
th3_MTL = ay >> ay >> localLocal (+10) (ay >> ay)
where ay :: (MonadCo r m, MonadReader r m) => m ()
ay = ask >>= yieldG
```

With the right primitives, combining complicated effects using monad
transformers can be straightforward. What I’ve presened here is not
evidence that monad transformers are the *best* way to go, but it does
show that more thought is needed before we declare them obsolete!

There are some further comparisons between extensible-effects and mtl.