summaryrefslogtreecommitdiffstats
path: root/site/opinions/MonadTransformers.org
blob: 0424c7b3342a382134b8dc94f8615cb225993876 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#+TITLE: Monad Transformers are a Great Abstraction

#+SERIES: ../opinions/index.html

#+BEGIN_EXPORT html
<p>This article has originally been published on <span
id="original-created-at">July 15, 2017</span>.</p>
#+END_EXPORT

#+BEGIN_EXPORT html
<div id="history">site/opinions/MonadTransformers.org</div>
#+END_EXPORT

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 [[https://blog.drewolson.org/elixirs-secret-weapon/][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 [[https://github.com/ogma-project][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.

#+BEGIN_SRC diff
-type Builder = StateT Text IO
+type Builder = StateT Text (ReaderT Language IO)
#+END_SRC

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:

#+BEGIN_SRC haskell
getLanguage :: Builder Language
getLanguage = lift ask
#+END_SRC

And that was basically it. The complete [[https://github.com/ogma-project/celtchar/commit/65fbda8159d21d681e4e711a37fa3f05b49e6cdd][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.