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
-
The
head
andtail
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
andtail
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 ofMaybe
. 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 ofhead
, for example, must beMaybe 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 ofhead
returningJust
the head of the list orNothing
, 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. ↩