---
title: Problem Solving with Maybe
teaser: Write more confident code to avoid viral `Maybe` taking over your project.
tags: elm,functional programming,good code,web
author: Joël Quenneville
published_on: 2018-02-12
---

[`Maybe`] has a tendency to take over your codebase. This property is sometimes
described as being [_viral_]. Uncertainty begets uncertainty. This sort of
defeats the goal of using `Maybe` in the first place: give you confidence in the
presence of most values.

By slightly changing our approach to solving problems involving uncertainty, we
can contain `Maybe` to those parts of our code that are truly optional.

![](https://images.thoughtbot.com/jq-working-with-maybe/kMzOcjT5QAKmgL5rAqKQ_virus.jpg)

[_viral_]: https://thoughtbot.com/blog/if-you-gaze-into-nil-nil-gazes-also-into-you
[`Maybe`]: https://thoughtbot.com/blog/maybe-mechanics

## The problem

Consider trying to get the uppercased first line of a friend's address when
you're uncertain whether the friend is present or not:

```elm
optionalFormattedFriendAddress : Maybe Friend -> Maybe String
optionalFormattedFriendAddress maybeFriend =
  let
    maybeAddress = case maybeFriend of
      Just friend -> Just friend.address
      Nothing -> Nothing

    maybeLine1 = case maybeAddress of
      Just address -> Just address.line1
      Nothing -> Nothing

  in
    case maybeLine1 of
      Just line1 -> Just (String.toUpper line1)
      Nothing -> Nothing
```

That's annoyingly complex and clunky 😰

Only the friend is optional but we're forced to make presence checks for each
operation in the chain. We could [refactor the case statements] into calls to
`map` but that's still making multiple presence checks, albeit in a much
prettier fashion.

What we need is a different approach to solving the problem.

[refactor the case statements]: https://thoughtbot.com/blog/maybe-mechanics

## Extracting functions

All this brings me to **Rule 1** when working with `Maybe`:

> Separate code that checks for presence from code that calculates values

Imagine you were in a perfect world where all the values were present. *Write
that function.*

Of course you live in the real world so now you need to write another function
that combines your "perfect" function with the `Maybe` helpers.

Looking at the previous example, in a perfect world, the friend is always
present. Let's write that function first.

```elm
formattedFriendAddress : Friend -> String
formattedFriendAddress friend =
  String.toUpper friend.address.line1
```

Then write a _separate function_ that handles the presence check:

```elm
optionalFormattedFriendAddress : Maybe Friend -> Maybe String
optionalFormattedFriendAddress maybeFriend =
  maybeFriend
    |> Maybe.map formattedFriendAddress
```

## Multiple independent optional values

So far we've only been dealing with a single optional value. What happens when
we need to deal with multiple values that might not be present? Let's say we
have two users who may or may not be present and we want to find what their
shared friends are?

![](https://images.thoughtbot.com/jq-working-with-maybe/AH72AcGnQ8KtF1q1Y8dg_friends-in-common.png)

**Rule 1** still applies. We start by writing a "perfect world" function that
assumes all values are present:

```elm
sharedFriends : User -> User -> Set Friend
sharedFriends user1 user2 =
  Set.intersect (Set.fromList user1.friends) (Set.fromList user2.friends)
```

Now we can write a separate function that checks for presence. Because there are
two `Maybe` values, we use [`Maybe.map2`].

```elm
optionallySharedFriends : Maybe User -> Maybe User -> Maybe (Set Friend)
optionallySharedFriends maybeUser1 maybeUser2 =
  Maybe.map2 sharedFriends maybeUser1 maybeUser2
```

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

## Adding in some uncertainty

Sometimes, even "perfect world" functions encounter uncertainty. **Rule 2**
gives us some clarity:

> The extracted function can _return_ `Maybe` but it may not accept `Maybe` as any
> of its arguments.

Say we wanted to find an optional user's most popular friend. By **Rule 1**, we
start by ignoring that the user might be optional and we assume they will be
present:

```elm
mostPopularFriend : User -> Maybe Friend
mostPopularFriend user =
  user.friends
    |> List.sortBy popularity
    |> List.reverse
    |> List.head
```

Event though we're following the "perfect world" rule, we're forced return a
`Maybe` here because the list of friends might be empty. That's OK by **Rule 2**
though.

Putting it all together, you'll notice we need to use `andThen` instead of
`map`. This is almost always the case when invoking **Rule 2**:

```elm
maybeMostPopularFriend : Maybe User -> Maybe Friend
maybeMostPopularFriend maybeUser =
  maybeUser
    |> Maybe.andThen mostPopularFriend
```

## Lots of uncertainty

Sometimes, you'll run into a situation where need to invoke **Rule 2** multiple
times. For example, you want to know where the most popular friend lived the
longest. The "perfect world" function starts by assuming that the friend is
present but there's no guarantee they've shared any addresses:

```elm
longestResidence : Friend -> Maybe Address
longestResidence friend =
  friend.addresses
    |> List.sortBy lengthOfStay
    |> List.reverse
    |> List.head
```

Unlike a [chain of `map` functions], we can't compose functions that invoked
**Rule 2**. Instead, we chain them together with `andThen`:

```elm
maybeMostPopularFriendLongestResidence : Maybe User -> Maybe Address
maybeMostPopularFriendLongestResidence maybeUser =
  maybeUser
    |> Maybe.andThen mostPopularFriend
    |> Maybe.andThen longestResidence
```

[chain of `map` functions]: https://gist.github.com/JoelQ/df3b6ac80b9623d797217a4e58cc3ad0

## Avoiding `Maybe` altogether

This whole discussion on avoiding the need for presence checks has skipped an
obvious solution: avoid `Maybe` altogether. This is **Rule 0**:

> Actively try to model your data structures to avoid `Maybe`

So is `Maybe` a bad thing? Should you start writing that _`Maybe` considered
harmful_ blog post? No.

`Maybe` tends to be overused, especially when coming from languages that mostly
deal with uncertainty with null. `Maybe` doesn't correlate one-to-one with uses
of null and there are often better ways to model your data.

## Optional lists

Take this model:

```elm
type alias Model =
  { numbers : Maybe (List Int)
  }
```

`List` already has an empty state: `[]`. Does `Just []` mean something different
than `Nothing` here? If it doesn't, we can can collapse this down to a `List`
without the wrapping `Maybe`.

Explore the idea more in this article on [alternatives to `Maybe (List a)`].

[alternatives to `Maybe (List a)`]: https://thoughtbot.com/blog/whats-weird-with-maybe-list

## Optional shapes

A common mistake is to try to model data that can have several shapes by
shoehorning the data into a record with a bunch of `Maybe` fields to handle
differences. For example, modeling a standard deck of cards:

```elm
type alias Card =
  { suit : Maybe Suit
  , rank : Maybe Rank
  }
```

The Joker is `Card Nothing Nothing`. We can do better by [modeling with union
types]:

```elm
type Card
  = Regular Suit Rank
  | Joker
```

[modeling with union types]: https://thoughtbot.com/blog/modeling-with-union-types

## Triangle of separation

![diagrams representing each of the 3 principles overlayed on the vertices of a triangle labeled "triangle of separation"](https://images.thoughtbot.com/6400cj233tculk1epho0zbu0r00h_triangle-of-separation.png)

I took the guidelines from this article (especially guideline 1) and made them
more generic to apply to all conditionals and edge cases rather than just
`Maybe` values. I ended up with three rules that are really just different
perspectives on the same idea. I call them the [triangle of separation](https://thoughtbot.com/blog/triangle-of-separation):

1. Write code at a single level of abstraction
2. Separate branching from doing code
3. Push conditionals up the decision tree


## Conclusion

`Maybe` is viral and can quickly overrun your codebase. By taking a step back
and considering _how_ you deal with uncertainty in your code, you can avoid a
lot of pain.

More concretely, the guidelines we've looked at lead to code that's mostly free
of presence checks:

> **Rule 0**:
> Actively try to model your data structures to avoid `Maybe`.

> **Rule 1**:
> Separate code that checks for presence from code that calculates values.

> **Rule 2**:
> The extracted function can _return_ `Maybe` but it may not accept `Maybe` as
> any of its arguments.
