---
title: The Mechanics of Maybe
teaser: Leverage the core mechanics of `Maybe` to clean up your nested cases.
tags: elm,functional programming,web
author: Joël Quenneville
published_on: 2018-02-09
---

Our world is full of uncertainty. This uncertainty bleeds into our programs. A
common way of dealing with this is [null/nil]. Unfortunately, this leads to even
more uncertainty because this design means any value in our system _could_ be
null unless we've explicitly checked it's presence. Constantly checking the
presence of every value is a lot of work so we tend to only check the riskiest
places and then have to deal with runtime [null exceptions] in the rest of our
code.

[null/nil]: https://thoughtbot.com/blog/if-you-gaze-into-nil-nil-gazes-also-into-you
[null exceptions]: https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/

## Maybe

[Elm] and many other languages use a different approach to dealing with
uncertainty: [`Maybe`].

In Elm, all values are _guaranteed_ to be present except for those wrapped in a
`Maybe`. This is a critical distinction. You can now be confident in most of
your code and the compiler will force you to make presence-checks in places
where values are optional.

[Elm]: http://elm-lang.org/
[`Maybe`]: http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Maybe

## Case statements

The most basic way of dealing with a `Maybe` is via a case statement:

```elm
case someOptionalValue of
  Just value -> -- do something if present
  Nothing -> -- do something if NOT present
```

This is your standard presence-check.

Unfortunately, this can quickly devolve into a nightmare of nested case
statements. For example, say you have a list of users and you want to uppercase
the name of the first friend of the first user. You'd end up with this
monstrosity:

```elm
case List.head users of
  Just user ->
    case List.head user.friends of
      Just friend ->
        Just (String.toUpper friend.name)

      Nothing ->
        Nothing

  Nothing ->
    Nothing
```

Luckily, the language can help us out with some helper functions.

## Helper functions - unwrapping

A few checks are so common that there are convenient helper functions for them
so you don't have to write manual case statements.

For example, when trying to unwrap an optional value you need to handle the case
where it isn't present. This is typically done by providing some default value.

```elm
case optionalNumber of
  Just number -> number
  Nothing -> 0 -- return the default 0 when we didn't have a value
```

With the helper function [`Maybe.withDefault`] it becomes

```elm
Maybe.withDefault 0 optionalNumber
```

[`Maybe.withDefault`]: http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Maybe#withDefault

## Helper functions - unwrapping and re-wrapping

Often there is no sensible default value so you just want to re-wrap in `Maybe`
after doing a calculation. Isolating the first step of our big case statement
(getting the friends of the first user) we might say:

```elm
case List.head users of
  Just user -> Just user.friends
  Nothing -> Nothing
```

This keeps propagating the `Maybe` forward down the chain. It's very common to
want to say "call this function if the value is present and return a new
`Maybe`" so there's a helper function we can use here: [`Maybe.map`]:

```elm
Maybe.map .friends (List.head users)
```

[`Maybe.map`]: http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Maybe#map

## Helper functions - nested maybes

Sometimes, the function you call when the value is present already returns a
`Maybe` so there's no need to re-wrap it in `Just`. Isolating the second step of
our big case statement (getting the first friend) might look like:

```elm
case maybeFriends of
  Just friends -> List.head friends -- no Just here
  Nothing -> Nothing
```

While it might look like we can use `map` here, there's one crucial distinction:
`map` re-wraps your successful computation with `Just`. That means using `map`
here would result in a doubly-nested maybe which is _not_ what we wanted.

Instead, we can use the [`Maybe.andThen`] function:

```elm
maybeFriends
  |> Maybe.andThen List.head
```

[`Maybe.andThen`]: http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Maybe#andThen

## A more extreme case

Looking at a more extreme case, say we wanted to get at the head of the inner
list `[[[[1, 2]]]]` using repeated uses of `List.head`:

```elm
[[[[1, 2]]]]
  |> List.head -- Just [[[1, 2]]]
  |> Maybe.map List.head -- Just (Just [[1,2]])
  |> Maybe.map (Maybe.map List.head) -- Just (Just (Just [1,2]))
  |> Maybe.map (Maybe.map (Maybe.map (List.head)) -- Just (Just (Just (Just 1)))
```

We got the head of the inner list but now it's deeply nested inside of `Maybe`s
so every step we need nested `map`s. This will not scale 😰

Consider the `andThen`-based implementation instead

```elm
[[[[1, 2]]]]
  |> List.head -- Just [[[1, 2]]]
  |> Maybe.andThen List.head -- Just [[1,2]]
  |> Maybe.andThen List.head -- Just [1,2]
  |> Maybe.andThen List.head -- Just 1
```

Notice that each step is only a single `Just` deep, no matter how many times we
chain.

## `map` vs `andThen`

So when do you want to use `map` vs `andThen`? A quick trick is to look at the
return type of the function you're passing in:

```elm
something -> Maybe somethingElse -- use andThen
something -> somethingElse -- Use map
```

In our example above, `.friends` returns a list so we can use it with `map`
while `List.head` returns a maybe so we need to use `andThen`.

## Nested case statements

When first using `Maybe`, you'll often end with large, nested `case` statements.
A closer look usually reveals that they conform to one of the three patterns
shown earlier.

Remember that nested case statement we started with?

```elm
case List.head users of
  Just user ->
    case List.head user.friends of
      Just friend ->
        Just (String.toUpper friend.name)

      Nothing ->
        Nothing

  Nothing ->
    Nothing
```

Nested branching logic, error handling, and duplicated cases make it very
difficult to follow the logic here. 😁

All those trailing `Nothing -> Nothing` cases are a giveaway that we can clean
this up using some of the helper functions. Combine them all into a pipeline and
we get:

```elm
users
  |> List.head
  |> Maybe.map .friends
  |> Maybe.andThen List.head
  |> Maybe.map .name
  |> Maybe.map String.toUpper
```

This is much nicer to read but is still equivalent to the case statement. In
part 2, we'll look at [problem solving with `Maybe`] and how changing the
structure of our logic can help push uncertainty to the edges of our system.

[problem solving with `Maybe`]: https://thoughtbot.com/blog/problem-solving-with-maybe
