Functors
For those of you that love math, the notion of a functor in Haskell is exactly the same as an endofunctor in the category of Haskell types.1 Pragmatically, to effectively program using functors and the other abstractions we will discuss soon, you don't need to understand what this sentence means; you can ignore it. I programmed effectively in Haskell for about 10 years before I finally opened a category theory book to understand the connection between Haskell functors and categorial functors, between Haskell monads and categorial monads, and so on.
To start, a functor is a type constructor with a single argument, and we can view it as a container storing values of this argument type. This includes
-
Lists: The list type
[a]
can be viewed as passing the type of the list elementsa
to the type constructor[]
. Maybe our earlier home-baked version of lists makes this a bit clearer. Those lists had the typeList a
, soList
is a type constructor with a single argumenta
. A list is also clearly a container type. -
Arrays: Arrays in Haskell have the type
Array i e
. They are parameterized by two types.i
is the type used to index into the array.e
is the type of elements stored in the array. If we fix the first type argument,i
, toInt
, for example, we obtainArray Int e
. This is still a type constructor, because we can still substitute anything fore
. So the partially applied type constructorArray Int
takes a single argumente
and produces from it an array type. Arrays are also containers. -
Maybe: It certainly fits the bill.
Maybe a
takes a single type argumenta
. However, how is it a container? We can look atMaybe
as a very simple sort of container. If we have a container of typeMaybe a
that isJust x
, then it stores a single valuex
. If it isNothing
, then it stores, well, nothing. So we can think aboutMaybe
as a container type that can store 0 or 1 elements. -
Identity:
Identity a
is a container that stores exactly one value of typea
. Here's the definition:newtype Identity a = Indentity a
This functor will play an important role towards the end of the next chapter, when we discuss monad transformers.
-
Binary trees: We can define a type
data Tree a = Empty | Node (Tree a) a (Tree a)
This says that a binary tree storing values of type
a
is eitherEmpty
or consists of a rootNode
that stores some value of typea
and with a left and right subtree of typeTree a
. Again, this matches our notion of a container—the tree stores some values in its nodes—and the type constructorTree a
takes a single argument. -
Functions that produce an
a
:newtype Reader r a = Reader { runReader :: r -> a }
Just as with arrays, this type constructor takes two arguments. We can fix the first one to, say,
Int
. This givesReader Int a
, which is a type constructor with a single argument left to fill in. SoReader Int
is a candidate to be a functor. How is it a container though? It clearly doesn't store any values of typea
, only a function of typeInt -> a
.But what's a function of type
Int -> a
? One way to think about it is as a container that stores a whole bunch ofa
s indexed byInt
values. Given a specificInt
value, applying the function to it "retrieves" the correspondinga
value.So our notion of containers is more conceptual than physical. The values of type
a
"stored" in the container don't have to be physically present, in the sense that they occupy some space in memory, but they must be retrievable in some fashion.
There are many more type constructors that are functors, but this list should suffice for now. You should look at all of them as container types, even though you may have to squint a bit to see how exactly they "contain" values.
Many container types are functors, but not all are. To be a functor, a container must be "well-behaved" in the sense discussed next.
-
This has the caveat that there is some debate whether Haskell types even form a true category, but even if they don't, the abstractions defined in category theory applied to something that's almost a category gets us very far when trying to bring order to the wealth of programming abstractions we use every day. ↩