After more than two years of writing production code using Haskell at Klarna. We’ve learned a ton. Initially, we used a `ReaderT`

pattern detailed in my “Haskell in Production” mini-series. We’ve now transitioned into using `MonadTrans`

and `MonadTransControl`

as means to write MTL without boilerplate.

In this post, we’re going to review the `ReaderT`

pattern we used, as well as go through its shortcomings and our chosen solution to it. Since a lot of people might only be interested in the solution, it is provided first.

Each interface has a corresponding `class`

:

```
class Monad m => MonadLog m where
-- | Print 'a' to the log with source code positions
logLn :: (HasCallStack, Loggable a) => LogLevel -> a -> m ()
```

which acts as the interface and allows us to write code that’s polymorphic in `m`

.

There’s a pass-through instance that is able to take any transformer monad `t`

that has an instance for `MonadLog`

on its base monad `m`

:

```
-- Pass-through instance for transformers
instance {-# OVERLAPPABLE #-}
Monad (t m)
( MonadTrans t
, MonadLog m
, => MonadLog (t m) where
) = lift (logLn level msg) logLn level msg
```

Having the `OVERLAPPABLE`

pragma on the pass-through instance means that any other instance we define would be chosen in preference to this one during instance resolution. This is described in the GHC user’s guide.

The instance above is used in order to provide instances for any transformer. Getting us past the dreaded `n^2`

issue! If you don’t know what that is - don’t worry, it’s explained further down.

Now it comes time to choose how to implement our effects. For each effect, there’s a newtype that constitutes the effect:

```
-- | Newtype for disabling logging
newtype NoLoggingT m a
= NoLoggingT { runNoLoggingT :: m a }
deriving newtype (Functor, Applicative, Monad)
deriving (MonadTrans) via IdentityT
instance Monad m => MonadLog (NoLoggingT m) where logLn _ _ = pure ()
```

This instance allows for us to choose not to log when we run our final program using the `runNoLoggingT`

function provided as a field in the newtype.

Here’s a real implementation of a console logger using fast-logger:

```
-- Transformer for logging to Console
newtype ConsoleLogT m a
= ConsoleLogT { unConsoleLogT :: ReaderT (LoggerSet, Trace) m a }
deriving newtype (Functor, Applicative, Monad, MonadTrans)
-- Instance using fast-logger to print to console
instance MonadIO m => MonadLog (ConsoleLogT m) where
= ConsoleLogT do
logLn level msg <- ask
(loggerSet, traceId) ConsoleLogger loggerSet) level (toLogItem msg)
logItem traceId (
runConsoleLogT :: MonadIO m => ConsoleLogT m a -> m a
ConsoleLogT m) = do
runConsoleLogT (<- liftIO (newStdoutLoggerSet defaultBufSize)
loggerSet Uncorrelated) runReaderT m (loggerSet,
```

Thanks to this structure, we can dispatch our effects at the “end of the world” by using the functions from each interface we want to use. The instances we’ve defined for `ConsoleLogT`

and `NoLoggingT`

are considered before the pass-through instance due to the `OVERLAPPABLE`

pragma.

Here’s an example from a service that logs things, submits metrics and reads messages from an SQS queue.

```
runProgram :: MetricsReporter -> QueueUrl -> AWS.Env -> IO ()
=
runProgram reporter queueUrl awsEnv = runConsoleLogT
. runMetricsT reporter
. runConsumerT queueUrl awsEnv
$ program
program :: (SqsConsumer m, MonadLog m, MonadMetrics m) => m ()
= ... program
```

This reminds us of how effects are dispatched using free monads, but by relying on something that’s well optimized by GHC and has strong support (MTL) in the community.

The rest of this post discusses how we got here and what we did before.

When starting out with Haskell, we didn’t want to overcomplicate things. `Reader`

is one of the first monads that our people got comfortable with.

Thus, our interfaces were all based on `ReaderT`

instances. This meant that essentially all interfaces required some data `r`

and were then based on `MonadIO m`

like:

```
-- | An interface to produce SQS messages
class Monad m => SqsProducer m where
-- | Produce messages to SQS, returning unit or an error in 'm'
produceMessage ::
SqsMessageGroupId -> SqsDedupeId -> SqsMessage -> m (Either SqsProducerError ())
data RequestSender m = RequestSender
_produceMessage ::
{SqsMessageGroupId -> SqsDedupeId -> SqsMessage -> m (Either SqsProducerError ())
}
instance
HasType (RequestSender m) r
( MonadCatch m
, MonadIO m
, MonadLog (ReaderT r m)
, => SqsProducer (ReaderT r m)
) = do
produceMessage groupId dedupeId message RequestSender produceMsg) <- asks getTyped
( lift (produceMsg groupid dedupeId message)
```

This gives us an end-of-the-world behavior where we need to do something similar to:

```
data ListenerContext m = ListenerContext
requestSender :: RequestSender m
{-- .. and other deps
}deriving stock (Generic)
runListener_ :: (MonadIO m) => ListenerContext -> m ()
= forever . runReaderT (void handleMessage)
runListener_
handleMessage :: SqsConsumer m => SqsProducer m => m Result
= do
handleMessage <- getMessage -- * Take a message from one queue
msg <- performAction msg -- * Perform some action
res -- * Put it on a different queue
produceMessage msg pure res -- * Return result of processing
```

in order to run our program. This is *fine*.

We’ve achieved what we want out of dependency injection:

- We can swap out the behavior of the interface by swapping out the
`RequestSender`

data type. E.g. allowing us to stub it in tests - We can write code in a polymorphic setting relying on interfaces rather than concrete implementations
- We have something that we thought was fairly easy for our devs to grok (ah, it’s a reader where the behavior is defined by the object we feed in, gotcha!).

There are a few drawbacks to this pattern. Let’s start with the most glaring issues:

Granular control over interfaces becomes tedious due to the extra indirection with the passed data

Certain things are difficult to implement, e.g:

`class Foo m where withCallback :: (a -> m b) -> m b`

This requires a

*lot*of lifting back and forth especially when the concrete implementation is in`IO`

and your interfaces are all in`m`

Error messages become vague and based on the instance constraints e.g:

`Couldn't satisfy constraint 'HasType (RequestSender m)'`

instead of the much more easily understandable:

`Missing instance 'SqsProducer (ReaderT r m)'`

In the latter, we can see that the instance is missing for the

`SqsProducer`

whereas in the former - we sort of need to do instance resolution by grep to figure out what class GHC is trying to construct an instance for.Lastly, and most important: it didn’t turn out to be so easy to grok as we thought

There are a couple of alternatives to this approach to dealing with effects. If you *want* effect tracking in your types - there are a number of libraries that deal with this:

Both of these are promising, but we’re not really comfortable with the drawbacks to either one at the moment. In a nutshell - they’re both great libraries, however, they’re pretty advanced.

Our old solution combined two things - and this was its main mistake. Either we should’ve said no to our interfaces and gone with something like the handler pattern - or we should’ve leaned fully into MTL.

The drawback with handler pattern is that we can’t be polymporphic, which we really like for testing. The drawback with MTL is the `n^2`

instances problem.

Urgh! From our wishes on polymorphism it’s clear we can’t use the handler pattern. But can we use MTL if we solve the `n^2`

issue? And what is the `n^2`

issue?

`n^2`

issueWhen using monad transformers, you need to write the monad instances for all the different types of transformers. Here’s an example from the MTL source code:

```
instance MonadState s m => MonadState s (ExceptT e m) where
= lift get
get = lift . put
put = lift . state
state
instance MonadState s m => MonadState s (IdentityT m) where
= lift get
get = lift . put
put = lift . state
state
instance MonadState s m => MonadState s (ReaderT r m) where
= lift get
get = lift . put
put = lift . state state
```

This is very mechanical and boilerplaty. For these common transformers, these instances have all been written. However, every time you add an additional transformer - you need to write all these `n`

instances where `n`

is the number of interfaces you intend to use. Thus the `n^2`

complexity.

For most of our monads that we create ourselves, they simply require this very mechanical boilerplate. This behavior looks like it could be captured by a typeclass (or two).

One of our engineers, Moisés, who previously worked for Standard Chartered introduced us to their solution to this issue.

Pepe Iborra commented on the PR for this post and provided the following insight into how the solution came about:

When I joined Strats in Jan 2017, the codebase was already making heavy use of type classes for individual effects, e.g.

`MonadTime`

,`MonadDelay`

,`MonadLog`

, etc. but there was no solution to the n^2 problem. Monad transformers were providing instances for all the effects, relying on deriving to avoid as much boilerplate as possible. Alexis article takes this approach to the extreme.I made the point that introducing a new effect class required adding it to the deriving lists of all N transformers, which made engineers unwilling to add effects. and the approach could not scale. My solution to this was the passthrough instance, which requires a

`MonadTransControl`

transformer (or`MonadtTrans`

for non-scoped effects). Since all`ReaderT`

transformers are in`MonadTransControl`

by definition unless the environment mentions the base monad, the codebase quickly gravitates towards`ReaderT`

in order to avoid having to write instances manually.

So, indeed, this can be captured by a typeclass. Enter `MonadTrans`

:

```
-- Pass-through instance for transformers
instance {-# OVERLAPPABLE #-}
Monad (t m)
( MonadTrans t
, MonadLog m
, => MonadLog (t m) where
) = lift (logLn level msg) logLn level msg
```

This instance can now lift any monad `m`

that implements `MonadLog`

into the transformer `t`

. This means no more having to write `n`

instances 🎉

As noted above, the `OVERLAPPABLE`

pragma allows us to control precedence for the pass-through instance, such that any other instance we define would be chosen in preference to it during instance resolution. This is described in the GHC user’s guide.

For the example above with a callback in `m`

, we can use `MonadTransControl`

as it has the ability to run something in the base monad. The real version of our `MonadLog`

has a function that allows you to specify a traceable ID that we call `CorrelationId`

:

```
class Monad m => MonadLog m where
-- | Print 'a' to the log with source code positions
logLn :: (HasCallStack, Loggable a) => LogLevel -> a -> m ()
-- | Correlate the 'm a' with the given correlation ID
correlatedWith :: CorrelationId -> m a -> m a
```

In our passthrough instance for this version of `MonadLog`

we now need to use `MonadTransControl`

:

```
-- Pass-through instance for transformers
instance {-# OVERLAPPABLE #-}
Monad (t m)
( MonadTransControl t
, MonadLog m
, => MonadLog (t m) where
) = lift (logLn level msg)
logLn level msg = do
correlatedWith corrId ma <- liftWith \runInBase ->
result
correlatedWith corrId (runInBase ma)pure result) restoreT (
```

We can leave out `MonadTrans`

since it’s implied by `MonadTransControl`

.

A full example was given in at the start of this post.

I hope this post presents a useful and comprehensible way to control effects in Haskell without deviating too much from standard language features.

Since writing this, I was pointed to Alexis’s article on making MTL typeclasses derivable. It’s a much more thorough article than mine and I greatly appreciated it.

- Add details on
`OVERLAPPABLE`

and their precedence in instance resolution (Moisés) - Add anchor to
`n^2`

issue when from where it was first mentioned (Moisés) - Add error message con to
`ReaderT`

section (Moisés) - Add Pepe Iborra’s account of how this came about at Standard Chartered