# The problem with mtl

Published on

This article starts a series in which I am going to publish my thoughts on and experience with different «extensible effects» approaches. This one in particular will explain the problem with the classic mtl approach that motivates us to explore extensible effects in the first place.

## How transformer stacks are born

Often we start with a single monad — perhaps `Reader`

or `State`

. Then we realize it would be nice to add more to it — other `ReaderT`

s or `StateT`

s, probably an `EitherT`

etc.

At that point writing the whole stack in a type signature becomes rather onerous. So we create a type alias for it, or even a newtype, to improve type error messages. At first it looks like a good idea — we have «the monad» for our application. It removes a lot of the cognitive overhead — all our internal APIs are structured around this monad. The more time we spend working on our application, the more useful functions we invent that are automatically compatible and composable; the more joy it becomes to write code.

At least this is how I used to structure my code. I learned this approach from xmonad, the first «serious» Haskell project I studied and took part in. It has the `X`

monad, and all the functions work in and/or with this monad.

## Concrete stacks are too rigid

This approach breaks, however, once we want to have multiple applications based on the same code. At work, for instance, I’d like to reuse a significant part of code between the real application, the simulator (kind of a REPL for our messaging campaigns) and tests. But those necessarily use different monad stacks! The simulator doesn’t deal with MySQL and RabbitMQ connections; the server doesn’t need to be able to travel back and forth in time, like our simulator does; and tests for a piece of functionality should ideally use the smallest stack that’s necessary for that functionality.

So we should abstract in some way from the monad stack.

## mtl’s classes

One such abstraction comes directly from `mtl`

, the monad transformers library.

If we simply write

```
{-# LANGUAGE NoMonomorphismRestriction #-}
import Control.Monad.State
import Control.Monad.Reader
= do
foo <- ask
x $ fromEnum $ not x put
```

without supplying any type signature, then the inferred type will be

`foo :: (MonadReader Bool m, MonadState Int m) => m ()`

This type signature essentially says that `foo`

is a monadic computation which has two effects: reading a boolean value and reading/writing an integral value. These effects are handled by the familiar «handlers» `runState`

and `runReader`

.

We can combine any such computations together, and the type system will automaticaly figure out the total set of effects, in the form of class constraints. E.g. if we also have

`bar :: (MonadState Int m, MonadWriter All m) => m ()`

then

`do foo; bar) :: (MonadReader Bool m, MonadState Int m, MonadWriter All m) => m () (`

So it looks like `mtl`

can provide us with everything that the «extensible effects» approach promises. Or does it?

## The limitation

Unfortunately, if we write something a little bit different, namely

```
{-# LANGUAGE NoMonomorphismRestriction #-}
import Control.Monad.State
import Control.Monad.Reader
= do
foo <- get
x $ fromEnum $ not x put
```

where we’ve changed `ask`

to `get`

, the compiler gets confused:

```
test.hs:6:3:
No instance for (Monad m) arising from a do statement
Possible fix:
add (Monad m) to the context of the inferred type of foo :: m ()
In a stmt of a 'do' block: x <- get
In the expression:
do { x <- get;
put $ fromEnum $ not x }
In an equation for ‘foo’:
foo
= do { x <- get;
put $ fromEnum $ not x }
test.hs:6:8:
No instance for (MonadState Bool m) arising from a use of ‘get’
In a stmt of a 'do' block: x <- get
In the expression:
do { x <- get;
put $ fromEnum $ not x }
In an equation for ‘foo’:
foo
= do { x <- get;
put $ fromEnum $ not x }
test.hs:7:3:
No instance for (MonadState Int m) arising from a use of ‘put’
In the expression: put
In a stmt of a 'do' block: put $ fromEnum $ not x
In the expression:
do { x <- get;
put $ fromEnum $ not x }
```

This is because mtl asserts, via a mechanism called functional dependency, that a monadic stack can have only once instance of `MonadState`

. Because `get`

and `put`

in the above example operate with different types of state, that code is invalid.

## Merging transformer layers

Since we can’t have multiple different `MonadState`

constraints for our reusable monadic computation, we need to merge all `StateT`

layers in order to be able to access them through the `MonadState`

class:

```
data MyState = MyState
_sInt :: Int
{ _sBool :: Bool
, }
```

Then we could generate lenses and put them in a class to achieve modularity:

```
class Has f t where
hasLens :: Lens t f
foo :: (MonadState s m, Has Int s, Has Bool s) => m ()
```

The drawbacks of this approach are:

- It is boilerplate-heavy, requiring an instance per field and a record per stack. When you need to convert between these records, it can be quite annoying.
- Since monad transformers don’t commute in general, you can’t always merge two
`StateT`

layers together. For instance, there’s no way to achieve the semantics of`StateT s1 (MaybeT (StateT s2 Identity))`

using only one layer of`StateT`

.

## Conclusion

mtl’s classes almost provide a valid «extensible effects» implementation, if not for the functional dependency that lets us have only single `MonadState`

instance per stack.

In the subsequent article we’ll explore ways to address this limitation.