Monad Transformers are a Great Abstraction

This article has originally been published on July 15, 2017.


This revisions table has been automatically generated from the git history of this website repository, and the change descriptions may not always be as useful as they should.

You can consult the source of this file in its current version here.

2021-03-28 2021 Spring redesign 495f9db
2020-08-30 Introducing the Opinions page 4393865
2020-07-14 Prepare the introduction of a RSS feed c62a61d
2020-02-21 Various improvement in cleopatra e792118
2020-02-16 Automatically generate a revision table from git history b47ee36
2020-02-05 Rename org posts 7c47d2b
2020-02-04 Change the date format 1f851ca
2020-02-04 Initial commit with previous content and a minimal theme 9754a53

Monads are hard to get right. I think it took me around a year of Haskelling to feel like I understood them. The reason is, to my opinion, there is not such thing as the Monad. It is even the contrary. When someone asks me how I would define Monads in only a few words, I say monads are a convenient formalism to chain specific computations. Once I’ve got that, I started noticing “monadic construction” everywhere, from the Rust ? operator to the Elixir with keyword.

Haskell often uses another concept above Monads: Monad Transformers. This allows you to work not only with one Monad, but rather a stack. Each Monad brings its own properties and you can mix them into your very own one. That you can’t have in Rust or Elixir, but it works great in Haskell. Unfortunately, it is not an easy concept and it can be hard to understand. This article is not an attempt to do so, but rather a postmortem review of one situation where I found them extremely useful. If you think you have understood how they work, but don’t see the point yet, you might find here a beginning of answer.

Recently, I ran into a very good example of why Monad Transformers worth it. I have been working on a project called ogma for a couple years now. In a nutshell, I want to build “a tool” to visualize in time and space a storytelling. We are not here just yet, but in the meantime I have wrote a software called celtchar to build a novel from a list of files. One of its newest feature is the choice of language, and by extension, the typographic rules. This information is read from a configuration file very early in the program flow. Unfortunately, its use comes much later, after several function calls.

In Haskell, you deal with that kind of challenges by relying on the Reader Monad. It carries an environment in a transparent way. The only thing is, I was already using the State Monad to carry the computation result. But that’s not an issue with the Monad Transformers.

-type Builder = StateT Text IO
+type Builder = StateT Text (ReaderT Language IO)

As you may have already understood, I wasn't using the “raw” State Monad, but rather the transformer version StateT. The underlying Monad was IO, because I needed to be able to read some files from the filesystem. By replacing IO by ReaderT Language IO, I basically fixed my “carry the variable to the correct function call easily” problem.

Retrieving the chosen language is as simple as:

getLanguage :: Builder Language
getLanguage = lift ask

And that was basically it. The complete commit can be found on Github.

Now, my point is not that Monad Transformers are the ultimate beast we will have to tame once and then everything will be shiny and easy. There are a lot of other way to achieve what I did with my Builder stack. For instance, in an OO language, I probably would have to add a new class member to my Builder class and I would have done basically the same thing.

However, there is something I really like about this approach: the Builder type definition gives you a lot of useful information already. Both the State and Reader Monads have a well established semantics most Haskellers will understand in a glance. A bit of documentation won’t hurt, but I suspect it is not as necessary as one could expect. Moreover, the presence of the IO Monad tells everyone using the Builder Monad might cause I/O.