Monads in Elm
Sometimes people ask, “Does Elm have monads?” And sometimes they ask, “What is a monad? I’ve heard that it is something mysterious and difficult.” This short essay attempts to answer these questions. We begin with an example.
1 Chaining Things Together
Elm has functions like these
andThen : (a -> Maybe b) -> Maybe a -> Maybe b
andThen : (a -> Parser b) -> Parser a -> Parser b
They are used to chain things together — things of type Maybe a
or type Parser a
in the case at hand. As an example, consider the problem of extracting the head of a list. For this we use the function
List.head : List a -> Maybe a
where
type Maybe a = Just a | Nothing
When a list is nonempty, List.head
returns a value of type Just a
. In the contrary case, it returns Nothing
. The example below illustrates this.
> List.head [1,2,3]
Just 1 : Maybe number> List.head []
Nothing : Maybe a
Now consider a deeply nested list, say [[1,2,3]]
. To extract the head of a list, it seems natural to use the function List.head
twice. Here is a start:
> [[1,2,3]] |> List.head
Just [1,2,3]
The notation x |> f
means f x
. Then g (f x)
can be written as a "pipeline," namely, as
x |> f |> g
Notice that [[1,2,3]] |> List.head
has Just [1,2,3]
as value. This is a Maybe
list, and so has the wrong type for a second application of List.head
. The solution is to use the function
Maybe.andThen : (a -> Maybe b) -> Maybe a -> Maybe b
Then successive applications of List.head
can be chained:
> [[1,2,3]] |> List.head |> Maybe.andThen List.head
Just 1 : Maybe number
The result, Just 1
, is a still a value of type Maybe number
. This is as expected, because we could have computed
> [[]] |> List.head |> Maybe.andThen List.head
Nothing : Maybe b
Notice how the Nothing
value is propagated down the pipeline.
2 A Pattern
The general pattern for andThen
, whether it be Maybe.andThen
or Parser.andThen
, or something else, is
andThen : (a -> M b) -> M a -> M b
Here M
, like Maybe
or Parser
, is a type constructor. Or, in Haskell terminology, a functor. Consider the function defined by permuting the arguments of andThen
:
b : M a -> (a -> M b) -> M b
Up to small differences of notation, this is the bind function in Haskell. It is usually written as an operator, so that
(>>=) : M a -> (a -> M b) -> M b
Bind is the function that makes a monad a monad.
For a type constructor (or functor) M
to be a monad, it suffices for there to be an associated bind function which satisfies certain properties to be discussed below. Said in the language of Elm, a monad is a type constructor that has an andThen
function satisfying certain properties.
3 Composition
Consider functions f: a -> b
and g : b -> c
. One can form the composite function g o f : a -> c
. More generally, composition can be formed whenever the type of the codomain of f
is the same as the type of the domain of g
.
Composition is a good thing. Why? Because composition is what gives us the power to make big things out of smaller things. In programming, that is important because we can reduce complicated things to simpler ones, thereby keeping both the code and ourselves sane.
Consider next the “monadic” functions
f : a -> M b
g : b -> M c
We cannot compose them as is because the types of the domain and codomain do not match. But observe what we can do with the help of the andThen
function:
g o' f = \x -> andThen g (f x)
The result is a new composition operator. It can be written in a more suggestive form that “follows the prose:”
g o' f = \x -> (f x) |> andThen g
Or one can also write it using the bind operator
g o' f = \x -> (f x) >>= g
That is, we use >>-
to “shove” the value f x
of type M b
into the function g
, which has type b -> M c
.
Aside. The construction of a new composition law is the point of view of the so-called Kleisli categories. Monadic functions form a category if one uses o'
instead of o
for composition.
4 Properties
There is a bit more to the monad story than has be recounted so far. The new composition law, like the usual one, should satisfy the identities
id o' f = f o' id = f
and
f o' (g o' h) = (f o' g) o' h
These identities translate back to identities for andThen
, M
, and a function a -> M a
that in Haskell is called return
. For Maybe
in Elm, that is the function that sends a value x
of type a
to Just x
. For Parser
, it is the function succeed : a -> Parser a
.
5 Summary
- Elm has monads.
- Monads are good because they make it possible to compose functions where this seems to be impossible.
- Monads are not mysterious: they are type constructors (or functors) with certain additional properties, one of which is to make a composition operation possible.
6 References
- Kleisli categories, in Bartosz Milewski’s Programming Cafe: Category Theory, Haskell, Concurrency, C++. Studying this reference was what finally turned on the lightbulb for me. My advice is to skip over the C++ parts and concentrate on the Haskell parts. Even better: his book (PDF).
- Joël Quenneville, The Mechanics of Maybe. I’ve adapted the example of deeply nested lists from this article.