Skip to content

Maybe

Haskell's Maybe type is known as Option or Optional in other programming languages, such as Java or Rust. It represents a value that may be present or absent. In Haskell, Maybe is defined as

data Maybe a
    = Nothing
    | Just a

A value of type Maybe a can store nothing at all (an absent value, Nothing), or it may store a value of type a, Just a. You can think of Maybe as a simple container type. Where an ArrayList<T> in Java can store an arbitrary number of values of type T, Maybe a stores zero or one value of type a. This view of Maybe as a container type will be important when talking about functors. So put a pin in it.

Note that Maybe is a type constructor, not a type by itself. It is parameterized by the type a of the value it may contain. So, Maybe Int may store Nothing or Just an Int. Maybe String may store Nothing or Just a String. And so on. In general, any type variable that occurs in at least one of the data constructors (here Nothing and Just) must occur as a parameter of the type constructor because we can build a variety of concrete types by replacing each of these type variables with a concrete type.

Let's play with our Maybe type a tiny little bit. Our intListHead and intListTail functions before were a little unsatisfactory. Taking the head or tail of an empty list shouldn't crash our whole program: we should have a way to recover from such a situation.1 Given that Maybe represents the presence or absence of a value, we can fix our intListHead function so it returns Nothing if the list is empty and Just the head if the list is non-empty:

intListHead :: IntList -> Maybe Int
intListHead Empty      = Nothing
intListHead (Cons x _) = Just x

We can similarly make intListTail return Nothing if the list is empty, in which case there is no tail, or Just the tail if the list is non-empty:

intListTail :: IntList -> Maybe IntList
intListTail Empty       = Nothing
intListHead (Cons _ xs) = Just xs

  1. The head and tail functions for the built-in list type in Haskell have exactly the same shortcoming. We call such functions partial functions because they are defined (do not crash) only for a subset of all possible arguments. head and tail are essentially undefined for the empty list. There exists a replacement for the Haskell standard library that replaces all of these partial functions with total functions, functions that are defined for all their arguments, mainly through generous use of Maybe. Programming with this standard library of total functions is safer, because if the compiler is happy with your program, it means that the program never crashes (but it may still misbehave). However, it also means that the return type of head, for example, must be Maybe a because the list may be empty, and then there is no head. Our code may take the head of a list that we know not to be empty, based on how our code constructed that list. To keep the compiler happy, our code still has to deal with the two possibilities of head returning Just the head of the list or Nothing, even though we know that the latter never happens in this context. Safety enforced at the type level always comes at the price of a little more verbosity. In most cases, it's worth the effort though.