Functors

Consider a list of natural numbers and a list of strings. Their element types differ, but the list structure and its structural operations remain the same. Let T\mathtt{T} denote the element type: the List\mathtt{List} constructor takes it and produces the concrete type List  T\mathtt{List\;T}. Therefore, the kind of List\mathtt{List} is \mathtt{* \to *}.

A C++ programmer may reasonably point out that the templates std::vectorT\mathtt{std::vector}\langle \mathtt{T} \rangle and std::listT\mathtt{std::list}\langle \mathtt{T} \rangle already abstract over the element type, while iterators and ranges let the same algorithms work with different structures, such as lists and trees. Those algorithms, however, abstract over the traversal interface of an existing object rather than over a type constructor that preserves the shape of a context.

Haskell takes the next step. Both List\mathtt{List} and Tree\mathtt{Tree} have kind \mathtt{* \to *}, so the Functor\mathtt{Functor} type class can abstract over the constructor f\mathtt{f} itself. The fmap\mathtt{fmap} operation takes a function ab\mathtt{a \to b}, transforms values inside f  a\mathtt{f\;a}, and returns f  b\mathtt{f\;b} while preserving the shape of the context. This is polymorphism not only over the element type but also over the context constructor. C++ can emulate it with templates and overloads, but the language has no direct standard counterpart to type classes and uniform higher-kinded polymorphism. The type class itself is declared as follows:

class Functor f where
  fmap :: (a -> b) -> f a -> f b   -- apply a function inside context f

In this declaration, f\mathtt{f} is a variable for a type constructor of kind \mathtt{* \to *}, not for a concrete type. The class specifies the common shape of fmap\mathtt{fmap}, while each instance defines how its context is preserved. The standard instances for lists and Maybe\mathtt{Maybe} work as follows:

instance Functor [] where
  fmap _ []       = []
  fmap g (x : xs) = g x : fmap g xs

instance Functor Maybe where
  fmap _ Nothing  = Nothing
  fmap g (Just x) = Just (g x)

As we can see, a Functor\mathtt{Functor} implementation depends on the context constructor but not on the concrete type of its elements. Now let us place the number 2 in a context. We begin with the key idea: a value in a context.

A plain value 2, a value in the context Just 2, and the empty context Nothing
Fig. 2.3. A plain value and a value in the Maybe\mathtt{Maybe} context: the “box” may contain a value (Just  2\mathtt{Just\;2}) or be empty (Nothing\mathtt{Nothing}).

A context lets us define the rules for exceptional cases once and then apply ordinary functions without scattering checks throughout an algorithm. More powerful abstractions built on this idea can chain computations and specify execution strategies, but Functor\mathtt{Functor} itself guarantees only transformation through fmap\mathtt{fmap}. Suppose a JavaScript computation returns undefined\mathtt{undefined} instead of a value. We then have to write an if\mathtt{if}; if the result is passed onward, such checks begin to spread through the algorithm. Haskell represents absence explicitly with Maybe\mathtt{Maybe}: its Functor\mathtt{Functor} instance leaves Nothing\mathtt{Nothing} unchanged and applies the function to the value inside Just\mathtt{Just}. Mapping over an empty list likewise returns an empty list. An ordinary function can be applied directly to a plain value; a value in a context would first have to be unpacked by hand. fmap\mathtt{fmap} takes care of that:

fmap applies a function inside a context: Just 2 becomes Just 5, Nothing remains Nothing, and a list is mapped element by element
Fig. 2.4. fmap\mathtt{fmap} applies a function to the value inside a context while preserving the context itself: an empty box stays empty, and a list is mapped element by element.
fmap (+3) (Just 2)   -- Just 5
fmap (+3) Nothing    -- Nothing

fmap\mathtt{fmap} has an infix synonym: g<$>xg \mathbin{\mathtt{<\$>}} x =fmap  g  x= \mathtt{fmap}\;g\;x.

Every instance must obey two laws: identity and composition.

fmap id == id                     -- identity
fmap (g . h) == fmap g . fmap h   -- composition

These laws mean that fmap\mathtt{fmap} changes values but not the shape of the context: it neither rearranges a list nor turns Just\mathtt{Just} into Nothing\mathtt{Nothing}. They also state that mapping two functions in sequence is equivalent to mapping their composition.

The main instances:

-- Maybe: the value may be absent
fmap (+3) (Just 2)       -- Just 5

-- A list: zero or more values; fmap = map
fmap (+3) [1, 2, 3]      -- [4, 5, 6]

-- Either e: only Right is mapped
fmap (+3) (Right 2)      -- Right 5
fmap (+3) (Left "oops")  -- Left "oops"

A functor is a mapping between categories that preserves id\mathtt{id} and composition. In the usual model of Haskell, functors are endofunctors on the category of types and functions, and the fmap\mathtt{fmap} laws correspond exactly to the functor axioms.