Functors: Mapping Over Context
The first proper Category Theory concept in Haskell: a Functor is anything you can map a function 'inside' — a list, a Maybe, a tree, a future computation — without changing its shape.
Starting from something familiar
You already know map:
map (+1) [1,2,3] -- [2,3,4]
map applies (+1) to every element of a list, without changing the fact that you still have a list — same length, same order, just transformed contents. The question a category theorist asks next is: what if that idea — “apply a function to the contents, leave the container’s shape alone” — makes sense for more than just lists?
class Functor f where
fmap :: (a -> b) -> f a -> f b
A Functor is any type constructor f (something like [], Maybe, or a tree) that supports fmap: given a function a -> b and an f a, produce an f b, by applying the function “inside,” wherever an a happens to live.
instance Functor Maybe where
fmap _ Nothing = Nothing
fmap f (Just x) = Just (f x)
fmap (+1) (Just 4) -- Just 5
fmap (+1) Nothing -- Nothing
fmap (+1) [1,2,3] -- [2,3,4] (lists are Functors too — this IS map)
Notice what stays constant across all three: Nothing maps to Nothing (you can’t apply a function to a value that isn’t there), Just stays Just, a 3-element list stays a 3-element list. fmap transforms the contents, never the shape.
The laws, and why they matter
Just declaring an fmap isn’t enough to be a lawful Functor — it also has to satisfy two equations:
Figure: The defining picture of a functor. Going right-then-down (f, then wrap in F) must land in the same place as going down-then-right (wrap in F, then fmap f) — the square always commutes.
The first law says mapping the identity function changes nothing — unsurprising, but important as a sanity check. The second law — sometimes called the composition law — says fusing two fmaps together is the same as one fmap of the fused function. This isn’t just tidy math for its own sake: it’s a promise to you, the caller. It guarantees fmap g (fmap f xs) and fmap (g . f) xs are genuinely interchangeable, so a compiler (or you, reasoning by hand) can always rewrite one into the other — the same equational reasoning from Chapter 5, now extended to work “through” a container.
The type-class definition of Functor doesn’t enforce these laws — Haskell can’t check them automatically, since they’re a semantic promise, not a type-level one. Writing an instance Functor that technically compiles but breaks the laws (say, an fmap that quietly drops elements) is entirely possible, and entirely a bug: it violates a contract every piece of code that uses Functor is silently relying on.
fmap on your own types
Functor instances aren’t limited to types the library already provides:
data Tree a = Leaf | Node (Tree a) a (Tree a)
instance Functor Tree where
fmap _ Leaf = Leaf
fmap f (Node l x r) = Node (fmap f l) (f x) (fmap f r)
exampleTree :: Tree Int
exampleTree = Node (Node Leaf 1 Leaf) 2 (Node Leaf 3 Leaf)
fmap (*10) exampleTree
-- Node (Node Leaf 10 Leaf) 20 (Node Leaf 30 Leaf)
fmap walks the whole tree, applying (*10) at every Node, and the tree’s actual shape — which branches go where — is completely untouched. GHC can even derive this instance for you automatically with {-# LANGUAGE DeriveFunctor #-} and deriving Functor, because the pattern above is entirely mechanical once you see it once.
The infix operator <$> is just fmap written as an operator, and it’s used constantly in real Haskell code: (*10) <$> exampleTree means exactly the same thing as fmap (*10) exampleTree. Once you’re used to reading it, f <$> x reads naturally as “f, applied inside x.”
The limit of Functor
fmap’s function only ever takes one plain argument. What happens when the function you want to apply is itself wrapped up — say, you have Just (+3) and want to apply it to Just 5? fmap alone can’t reach that; Just (+3) isn’t a bare function, it’s a function inside a Maybe. That gap is exactly what the next vantage point, Applicative, exists to close.
In category theory proper, a functor is a mapping between two categories that preserves structure: it sends objects to objects, morphisms to morphisms, and respects identity and composition — precisely the two laws above. Haskell’s Functor typeclass is the special case where both categories are Hask (types and functions) itself, making F an endofunctor. This is the same “endo-” you’ll meet again if you ever hear the joke that “a monad is just a monoid in the category of endofunctors” — a real, if famously unhelpful-on-first-read, one-line definition of Chapter 11’s subject.