---
title: Running Out of Maps
teaser: What happens when you need a bigger mapN?
tags: elm,web,functional programming
author: Joël Quenneville
published_on: 2021-05-12
---

Many Elm packages provide `map2`, `map3`, `map4`, etc functions. No matter how
many of these the package author has provided, inevitably someone will end up
needing a `mapN` larger than those included in the package. Perhaps that
"someone" is you. How do you deal with a situation where you run out of maps?

## The problem

You have a record that describes a user. It has 5 fields.

```elm
type alias User =
  { name : String
  , age : Int
  , address : String
  , email : String
  , role : Role
  }

type Role = Admin | Regular
```

The data you are using to construct the user comes from an uncertain source so
any individual piece of data could be absent. If all the pieces of data are
present, you want to construct a `User` with them, otherwise you want to get a
`Nothing` back.

Since the `User` constructor is a 5-argument function and you have 5 maybe
pieces of data, you can use the `Maybe.map5` function. No problem!

```elm
Maybe.map5 User
  (Just "Alice")
  (Just 42)
  (Just "41 Winter Street")
  (Just "alice@example.com")
  (Just Regular)

-- Just
--   { name = "Alice"
--   , age = 42
--   , address = "41 Winter Street"
--   , email = "alice
--   , role = Regular
--   }
```

Now a new requirement has come in. You need to store the user's language
preference as a 6th field on the record. That should be pretty straightforward.
The new `User` constructor will be a 6-argument function and now well have 6
maybe pieces of data. Just swap out the `map5` for a `map6`.

But wait! The `Maybe` library only goes up to `map5`. There is no `map6`!

## One-liner to rule them all

The solution to all your mapping problems is this little helper. It is the key
to being able to map an arbitrarily large number of optional values.

```elm
andMap = Maybe.map2 (|>)
```

## Solving the problem, pipeline-style

The `andMap` function allows us to take the same pieces as an equivalent `mapN`
functions (here a 6-argument function and 6 pieces of optional data) but
organizes them slightly differently using a **pipeline style**. It ends up
looking like:

```elm
Just User
  |> andMap (Just "Alice")
  |> andMap (Just 42)
  |> andMap (Just "41 Winter Street")
  |> andMap (Just "alice@example.com")
  |> andMap (Just Regular)
  |> andMap (Just "en-us")
```

You start by wrapping your function in `Just`, then pipe to `andMap` for each of
your pieces of optional data. Note that order is important here. The optional
values need to be piped to in the same order as the arguments to `User`.

This can be extended to any arbitrary size to adding more pipes to the chain. No
more limitations!

## Other types

Mapping over multiple values is a [common pattern for many types], not just
`Maybe`. You can use this pipeline pattern for them as well. This technique
works for any type that defines a `map2` and some way to "wrap" the initial
function. This works even if the type's internals are private. Running out of
map functions for a random generator? No problem!

```elm
andMap = Random.map2 (|>)

userGenerator : Random.Generator User
userGenerator =
  Random.constant User
    |> andMap nameGenerator
    |> andMap ageGenerator
    |> andMap addressGenerator
    |> andMap emailGenerator
    |> andMap roleGenerator
    |> andMap languageGenerator
```

Need to decode a large JSON object? [Pipeline decoding] is a popular solution
that builds on `andMap`. Any time you're running out of map functions, `andMap`
is there to save the day!

If you're just looking for a fix to the problem of running out of map functions,
feel free to stop reading here. You've got a solution! If you're curious about
_why_ this works, get ready for a deep dive to see what's happening behind the
scenes.

[common pattern for many types]: https://thoughtbot.com/blog/elms-universal-pattern
[Pipeline decoding]: https://thoughtbot.com/blog/pipeline-decoders-in-elm

---

## Under the hood

When we introduced `andMap` earlier, it was as a terse one-liner. What's
actually happening there? Here's what a long-form version might look like:

```elm
andMap : Maybe a -> Maybe (a -> b) -> Maybe b
andMap maybeItem maybeFunction =
  case (maybeItem, maybeFunction) of
    (Just item, Just function) ->
      Just (function item)

    _ ->
      Nothing
```

This function takes two arguments: a _value_ and a _function_, both wrapped in
`Maybe`s. If both are present it unwraps them, applies the function to the
argument, and re-wraps the result in `Just`. In effect, this allows us to pass
an argument to a function, even if both are wrapped in `Maybe`.

## How it works

This `andMap` function we've built allows us to apply a function to a value
where both are inside a `Maybe` (that is to say we're uncertain whether they are
present). Let's try it out!

```elm
andMap (Just 3) (Just \x -> x + 1)
-- returns `Just 4`
```

We could also pipe in the arguments like. On the first line we have our
incrementing function wrapped in a `Maybe`.

```elm
Just (\x -> x + 1)    -- Maybe (Int -> Int)
  |> andMap (Just 3)  -- Maybe Int
 -- returns `Just 4`
```

We can expand this to work with multi-argument functions by taking advantage of
Elm's [partial application]. If we call `andMap` with a 2-argument function, we
will get back a Maybe 1-argument function. We've seen above that we can apply an
argument to that kind of function by piping to `andMap`.

```elm
Just add              -- Maybe (Int -> (Int -> Int))
  |> andMap (Just 3)  -- Maybe (Int -> Int)
  |> andMap (Just 2)  -- Maybe Int
```

Each pipe to `andMap` applies an argument to the function. If the function
needs more arguments it will return a new function. We can keep chaining
`andMap` until all of the arguments have been applies and we are left with the
final value. That's the magic behind the pipeline style!

[partial application]: https://guide.elm-lang.org/appendix/function_types.html

## Where does the one-liner come from?

Now you know how `andMap` works but how does it relate to the one-liner shown at
the beginning of the article?

Here is the expanded version again:

```elm
andMap : Maybe a -> Maybe (a -> b) -> Maybe b
andMap maybeItem maybeFunction =
  case (maybeItem, maybeFunction) of
    (Just item, Just function) ->
      Just (function item)

    _ ->
      Nothing
```

If this unwrap two maybes, do a thing, re-wrap pattern looks familiar, it's
because that's what the [various map functions do]. We can replace that case
expression with a `Maybe.map2`. The code still works the same.

```elm
andMap : Maybe a -> Maybe (a -> b) -> Maybe b
andMap maybeItem maybeFunction =
  Maybe.map2 (\item function -> function item)
    maybeItem
    maybeFunction
```

We can reduce this even further! That lambda applies a function to an argument.
It acts just like the [forwards pipe `|>`] operator.

```elm
(\item function -> function item)

-- same as
item |> function
```

This means we can reduce our function to:

```elm
andMap : Maybe a -> Maybe (a -> b) -> Maybe b
andMap maybeItem maybeFunction =
  Maybe.map2 (|>)
    maybeItem
    maybeFunction
```

Eliminate the arguments with [point-free style] and drop the signature and you
get the terse one-liner shown earlier in the article.

```elm
andMap = Maybe.map2 (|>)
```

[various map functions do]: https://thoughtbot.com/blog/two-ways-of-looking-at-map-functions
[forwards pipe `|>`]: https://package.elm-lang.org/packages/elm/core/latest/Basics#(|%3E)
[point-free style]: https://medium.com/wat-the-elm-ist/its-a-point-free-world-4ce9e1e89b74

## Symmetry

The `andMap` and `map2` functions are two sides of the same coin. Both functions
can be implemented in terms of the other.

```elm
andMap : Maybe a -> Maybe (a -> b) -> Maybe b
andMap =
  Maybe.map2 (|>)
```

```elm
map2 : (a -> b -> c) -> Maybe a -> Maybe b -> Maybe c
map2 function maybe1 maybe2 =
  Just function
    |> andMap maybe1
    |> andMap maybe2
```

In fact, you can implement _any_ `mapN` function in terms of `andMap`. You wrap
your `n`-argument function in `Just` and follow it with `n` pipes to `andMap`.
For example if we wanted create an actual `map6` function we could write it as:

```elm
map6 : (a -> b -> c -> d -> e -> f -> g)
  -> Maybe a
  -> Maybe b
  -> Maybe c
  -> Maybe d
  -> Maybe e
  -> Maybe f
  -> Maybe g
map6 sixArgFunction maybe1 maybe2 maybe3 maybe4 maybe5 maybe6 =
  Just sixArgFunction
    |> andMap maybe1
    |> andMap maybe2
    |> andMap maybe3
    |> andMap maybe4
    |> andMap maybe5
    |> andMap maybe6
```

## Applicatives

If you read more formal functional programming literature, you may run into the
term **applicative**. These are defined as entities that have all of the
following:

1. a constructor
2. either `map2` OR `andMap` defined for it

The `Maybe`, `Json.Decode.Decoder`, and `Random.Generator` types all meet these
criteria and thus can be described as being applicative.

Applicatives have many interesting properties. The one we've explored in this
article is tied to the definition itself: `map2` and `andMap` are equivalent to
each other and each can be defined in terms of the other. All other `mapN`
functions can be built out of either of these.
