The Meaning of Map in Elm
Understanding and using Elm’s many
TL;DR; map is not just for lists, you can use map to change the values inside structures like Maybe, Result and even Decoder whilst preserving that structure.
I’ve been enjoying writing Elm a lot recently. One of my favourite features of Elm is its type system. It’s taken me some time to get comfortable with Elm’s types, but the more I get to know them the more I love them.
Alongside learning Elm I’ve been reading through the excellent Haskell Book, and it’s definitely given me a deeper understanding about using types that I’d like to share, particularly about using map - (Don’t worry there won’t be any crazy category theory speak).
map is through
> [ 1, 2, 3, 4, 5 ].map(x => x * 2)
> [ 2, 4, 6, 8, 10 ]
Map is great ❤️.
From an Array to a List
List in Elm then seems like the most obvious comparison to a JS array, it’s a bunch of values with the same literal brackets
 and you can apply a function to each of them with
List.map. If you’ve used map in JS, the example below should look reasonably similar:
> List.map (\x -> x * 2) [ 1, 2, 3, 4, 5 ]
> [ 2, 4, 6, 8, 10 ] :: List number
But what about
Result.map? And what on earth do
List.map3 do? What does it even mean to “map” over a
Result or a
Maybe and why would we want to do this?
Over the Wall
Imagine for a second that the square brackets of the List are walls. Imagine also that our double function
\x -> x * 2 has stubby little legs! Try as it might the poor thing can’t get over the wall!
map to “lift” the function over the wall of the List to reach the values inside it. The wall is still intact after but the values are now changed.
map gave our double function wings!
Just get lifted
Now say that instead of a List we’ve been given a value which is a
Maybe Int. It could be
Just an integer or it might be
Nothing. That’s all fine and well but what if we want to change the value inside the
Just (how about doubling it, we’ve got a function that does that already right!) but preserve the fact that the integer might not be there? We might just pull it apart with a case expression:
If we get
Just a value, double the value inside the
Just and return it wrapped in another
Just, and if we get
Nothing just return the
maybeDouble : Maybe Int
case maybeValue of
Just value ->
Just (double value)
maybeValue : Maybe Int
double : number -> number
double x =
x * 2
This is fine if we only have to do it once but gets kinda long writing out those case expressions if we have to do it lots of times. It turns out there’s a handy little pattern for this, and it’s called map! This is
Maybe.map from the Elm Core source code, looks kinda similar to our maybeDouble above right?
map : (a -> b) -> Maybe a -> Maybe b
map f maybe =
case maybe of
Just value ->
Just (f value)
And here’s how we’d use it to get our maybeDouble value:
maybeDouble : Maybe Int
Maybe.map double maybeValue
Maybe.map takes our ordinary, stubby legged double function and “lifts” it over the wall of the
Maybe so it can reach the value inside. The difference here is that if you give
Nothing it just returns it as is. Imagine similarly if you gave
List.map an empty list, it would just return the list unchanged. Starting to see the pattern?
Rethinking what map is
Stepping back from a List we can now think of map as:
a way to apply a function over or around some structure that we don’t want to alter.
That is, we want to apply the function to the value that is “inside” some structure and leave the structure alone. — Haskell Book
Decoder are all pieces of structure that hold onto values, we can use map on any of them to reach inside and change those values.
For a more realistic example, say we’re getting some json data back from a server: one of the fields is a date as the number of milliseconds since Jan 1 1970. It would be nice if we could turn this into an Elm date instead rather than having to faff around with it later in other bits of our application that need it as an Elm date.
Json.map to the rescue!
import Date exposing (fromTime)
import Json.Decode as Json
-- Json.int : Decoder Int
dateDecoder : Decoder Date
Json.map fromTime Json.int
-- or in pipeline style
dateDecoderPipe : Decoder Date
Json.int |> Json.map fromTime
Great! We can now use our
dateDecoder instead of the
int decoder to turn the server data directly into an Elm date.
Decoder may seem a little more abstract than something like a
Maybe (at least I still find it a little abstract). But just remember, take a step back, look at the type signature:
Decoder is the structure and the
Int is the value. We can lift a plain old function over the
Decoder wall and change the
Int inside it.
You may be thinking, this seems like a lot of fanfare about a simple function. However, I think understanding map in this way helps us get to grips with some of the reasons why Elm is such a powerful language.
Elm reminds us that a lot of our data — if not nearly all of it — lives in a context. Just to name a few:
- The data might be a collection of values
- Values might not be there
- Computations might screw up if they’re given a bad value
- Web requests might fail for a bunch of reasons
Data always comes from somewhere.
Elm gives us the power to describe our data with this extra level of richness. Using types this way brings so much reality and truth to our data models; we have to face the hard truths of our data up front, but it gives us the power to handle all sorts of messy and weird situations gracefully and robustly.
map is one of the tools that makes working with and preserving these rich contexts easier. We can reuse plain functions in all sorts of different contexts! Now go forth and lift your functions to a higher state of consciousness! 🌀😎🌻
We’ll have a look at some structures that have more than one embedded value (Like
map3 and even the mysterious
Some Random Final bits
If you’ve understood most of this article, you know what a Functor is! Congratulations you category theory whizz!
In Haskell, the equivalent of Elm’s versions of map is called
flatmap, and any value of a type that implements
flatmap properly is a
The authors of Haskell Book love George Clinton, “one of the most important innovators of funk music”, and the album “Mothership Connection”:
“You can pretend the album is mapping your consciousness from the real world into the category of funkiness if that helps.”