# Problem Solving with Maybe

`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.

## 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:

``````optionalFormattedFriendAddress : Maybe Friend -> Maybe String
let
Nothing -> Nothing

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.

## 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.

``````formattedFriendAddress : Friend -> String
``````

Then write a separate function that handles the presence check:

``````optionalFormattedFriendAddress : Maybe Friend -> Maybe String
maybeFriend
``````

## 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?

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

``````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`.

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

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:

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

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:

``````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:

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

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

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

## 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:

``````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)`.

## 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:

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

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

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

## 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.