Maybe and Either

Dear Computer

Chapter 9: Types Revisited

Maybe and Either

Now that we've seen the data command, let's revisit a type that we saw earlier: Maybe. Recall that Maybe is used to represent data that may be null or undefined. It's a builtin type in Haskell, but we could have written it ourselves like this:

Haskell
data Maybe a = Nothing | Just a

When a computation cannot produce a value, we return Nothing. Otherwise, we return the value wrapped up in a Just. Since we don't know what the type of the wrapped value is going to be, we target generic type a.

Earlier we discussed headMaybe, divMaybe, and readMaybe. Let's look at a new example. Suppose we want to find the first element in a list that meets some criteria. Our function takes in a boolean predicate and a list, and it returns one of the items. This seems like a reasonable type signature:

Haskell
find :: (a -> Bool) -> [a] -> a

But what if no item satisfies the predicate? We'll get to the end of the list and have no a to return. We need to return a Maybe since the search may fail:

Haskell
find :: (a -> Bool) -> [a] -> Maybe a

When our search reaches the empty list, there's no item that meets the criteria, and we give back Nothing:

Haskell
find _ [] = Nothing

In the general case, the list is not empty. We check the first item to see if it satisfies the predicate. It it does, we return it wrapped up in a Just. Otherwise we recurse on the tail:

Haskell
find predicate (first : rest)
  | predicate first = Just first
  | otherwise = find predicate rest

Thanks to the Maybe, anyone who calls find is forced to contend with the possibility of a failed search. The type system demands that the caller check if the return value is Nothing or Just. In a language like Java, programmers often do not check if a returned reference is null before trying to call a method on it or access its data. These programmers are usually operating in haste or under the naive optimism that errors don't occur. Their programs inevitably crash with a NullPointerException. Programmers who use maybe types won't trigger such exceptions. They can only get at the data if they've matched it against Just.

When we receive Nothing back from a function, we know that something went wrong, but not what. Haskell has another builtin type called Either that carries an explanation. In its most abstract sense, it is just a tagged union that holds either a Left value or a Right value. But in the conventional context of error handling, a Left value is the error message that explains why the data couldn't be computed, and the Right value is the data that was successfully computed. Its definition looks something like this:

Haskell
data Either a b = Left a | Right b

This alternative definition of find gives back an Either:

Haskell
find :: (a -> Bool) -> [a] -> Either String a
find _ [] = Left "No element matched the predicate"
find predicate (first : rest)
  | predicate first = Right first
  | otherwise = find predicate rest

When no item is found, it gives back a Left with the error message. When a matching item is found, it is returned wrapped up in a Right. As with Maybe, the caller must pattern match against the variants before it can operate on the result. The type system forces the caller to acknowledge that errors occur.

We will see types similar to Maybe and Either used frequently in Rust, a language whose designers value software safety.

← Generic TypesFunctions or Objects →