---
title: Two ways of looking at map functions
teaser: Map functions are hard to "get" in the abstract. This looks at two mental
  models that helped me understand them better.
tags: elm,web,functional programming
author: Joël Quenneville
published_on: 2017-11-20
---

When learning a functional language, you'll notice that `map` functions are
everywhere. Coming from Ruby, I wasn't familiar with map functions other than
[`Array#map`].

[`Array#map`]: https://ruby-doc.org/core-2.4.2/Array.html#method-i-map

Now the best way to learn these is to actually use them and get a feel for what
they do. After doing that, I noticed that among other things map functions solve
two big problems:

1. It's tedious to wrap/unwrap data structures just to run functions on the
   values inside them.
2. We need a way to translate "normal" functions we write to work on wrapped
   values like `Maybe` and `Result`.

If you squint, you may notice that these two problems are actually the same
problem viewed from two different angles. So think of this as two different
perspectives on mapping functions.

## Problem 1 - Wrapping and unwrapping

You have a [wrapper
type](https://thoughtbot.com/blog/lessons-learned-avoiding-primitives-in-elm)
and want to run a function on the value inside:

```elm
type Dollar = Dollar Int
```

Incrementing the dollar value looks like:

```elm
incrementDollar : Dollar -> Dollar
incrementDollar (Dollar d) =
  Dollar (d + 1)
```

I'm [destructuring](https://gist.github.com/yang-wei/4f563fbf81ff843e8b1e) in
the arguments to unwrap the value inside the `Dollar`. That's a lot of wrapping
and unwrapping needed to do something simple.

Trying to double a dollar looks similar:

```elm
doubleDollar : Dollar -> Dollar
doubleDollar (Dollar d) =
  Dollar (d * 2)
```

in both cases, we're following the same three steps:

1. Unwrapping the integer
2. Doing something with the integer
3. Re-wrapping the result from step 2

Step 2 is the only part that's interesting. Steps 1 and 3 are a
boilerplate-heavy wrap/unwrap sandwich.

There's a principle in software development that states:

> Separate things that change from things that stay the same.

We can abstract away the wrapping/unwrapping sandwich:

```elm
mapDollar : (Int -> Int) -> Dollar -> Dollar
mapDollar fn (Dollar d) =
  Dollar (fn d)
```

This looks just like our other two functions but now you can pass it an
arbitrary function to use for step 2.

```elm
incrementDollar : Dollar -> Dollar
incrementDollar dollar =
  mapDollar (\d -> d + 1) dollar
```

### Handling multiple arguments

What about multiple arguments? How about trying to add two dollar amounts?

```elm
addDollar : Dollar -> Dollar -> Dollar
addDollar (Dollar d1) (Dollar d2) =
  Dollar (d1 + d2)
```

We're still doing three steps:

1. Unwrap the two integers
2. Calculate something based on the two integers
3. Re-wrap the result of step 2

This is just like our `mapDollar` before but now we work with two arguments.

```elm
map2Dollar : (Int -> Int -> Int) -> Dollar -> Dollar -> Dollar
map2Dollar fn (Dollar d1) (Dollar d2) =
  Dollar (fn d1 d2)
```

Now we can say:

```elm
addDollar : Dollar -> Dollar -> Dollar
addDollar d1 d2 =
  map2Dollar (+) d1 d2
```

## Problem 2 - Translating functions

You've written some functions for some basic number transformations:

```elm
increment : Int -> Int
increment n =
  n + 1

add : Int -> Int -> Int
add n1 n2 =
  n1 + n2
```

This works fine until you inevitably come across a number wrapped in `Maybe`.
Great, now you have to re-implement versions of these functions that work on
`Maybe` values. And you'll probably have to do that again to deal with numbers
wrapped in `Result`. If only there was a way to auto-translate your functions.

Well there is!

Notice that the signature of the functions is the same other than possibly being
wrapped in `Maybe` or `Result`.

```elm
increment       :          Int ->          Int -- HAVE
incrementMaybe  : Maybe    Int -> Maybe    Int -- WANT
incrementResult : Result a Int -> Result a Int -- WANT
```

Enter the `map` function:

```elm
-- TRANSLATE increment TO WORK ON MAYBE

incrementMaybe : Maybe Int -> Maybe Int
incrementMaybe =
  Maybe.map increment
```

```elm
-- TRANSLATE increment TO WORK ON RESULT

incrementResult : Result a Int -> Result a Int
incrementResult =
  Result.map increment
```

![maybe map increment](https://images.thoughtbot.com/jq-looking-at-map-functions/KqAEJXfLTdmvb5mmLwL8_maybe-map-increment.png)

### Multiple arguments

That's cool but what about our `add` function? It takes _two_ arguments. That's
what `map2` is for (and `map3` for 3-arg functions and so on).

Again, the signatures are similar other than the wrappers:

```elm
add :                Int ->          Int ->          Int -- HAVE
addMaybe  : Maybe    Int -> Maybe    Int -> Maybe    Int -- WANT
addResult : Result a Int -> Result a Int -> Result a Int -- WANT
```

Using `map2`, this would look like:

```elm
-- TRANSLATE add TO WORK ON MAYBE

addMaybe : Maybe Int -> Maybe Int -> Maybe Int
addMaybe =
  Maybe.map2 add
```

```elm
-- TRANSLATE add TO WORK ON RESULT

addResult : Result a Int -> Result a Int -> Result a Int
addResult =
  Result.map2 add
```

![maybe map2 add](https://images.thoughtbot.com/jq-looking-at-map-functions/CglOlXM1TXenmIktLy5t_maybe-map2-add.png)

## Patterns

Map functions are some of the most versatile and useful constructs in functional
programming. As you work with them, you'll start getting a feel for them.
Perhaps you're using them to [make presence checks]. Perhaps you're using them
to [layer wrappers on other values].

Eventually some larger patterns emerge. You'll start seeing how all these
perspectives are just that: the same solution viewed from a different angle.
With each new perspective on these little functions, you gain a clearer
understanding of how where they fit in the world and are better able to see the
problems that are best solved with a `map`.

[make presence checks]: https://thoughtbot.com/blog/problem-solving-with-maybe
[layer wrappers on other values]: https://thoughtbot.com/blog/elms-universal-pattern
