If you've ever called .map on a List or Option, you've used a functor. The shape changes — the idea doesn't. Let's see why List, Option and Either are all the same animal in different clothes.
In Chapter 1 we said categories are just objects and arrows that compose, obeying two laws — identity and composition. In this chapter we meet our first real structure built on top of those laws: a way to take a function on plain values and "lift" it so it operates on values trapped inside a container.
You've been doing it for years. Every time you wrote List(1,2,3).map(_ + 1) you used the only operation a functor offers. The cast: a container F[A] and a function A => B. The plot: get back an F[B] — same shape, transformed contents. The rules: don't cheat (we'll see what cheating looks like).
A functor is a container with a "map a function over my contents" button. The button is called .map in Scala and fmap in Haskell. Same idea, two names.
Three quick examples:
List(1, 2, 3).map(_ + 1) = List(2, 3, 4) — the function hits every element.Some(5).map(_ * 2) = Some(10) — if there's a value, transform it.None.map(_ * 2) = None — empty box passes through untouched.Notice what stays the same: the container's shape. A list of three stays a list of three. A None stays a None. Only the things inside change — and they only change if there are any to change.
The formal name for any container that ships with this kind of well-behaved .map is a functor.
Back in Chapter 1 we boiled categories down to identity and composition. A functor earns its name by preserving both when it lifts functions. That's the whole reason the word exists.
If you map the do-nothing function over a container, you get back exactly what you started with. No surprises, no extra work, no rearranging.
xs.map(x => x) == xs
Mapping f and then mapping g has to be the same as mapping the combined function g ∘ f in one pass. The container can't sneak in extra work between the two steps.
xs.map(f).map(g) == xs.map(g compose f)
In Haskell-speak: fmap g . fmap f == fmap (g . f). Same equation.
.map predictable. A compiler, a teammate, or your future self can rewrite xs.map(f).map(g) as xs.map(g compose f) and know nothing breaks. Any half-clever map-shaped function that breaks the laws is not a functor — even if its type signature fits.Different shapes, identical move. Each container has its own answer to "how do I apply a function to my contents?" — but they all respect the two laws.
List[A] — zero or more A's. .map applies f to each element, position preserved.Option[A] — zero or one A. .map applies f if there's a value; if it's None, the box stays empty.Either[E, A] — exactly one value, either an A on the Right or an E on the Left. .map only touches the Right side. Lefts pass through."Right-biased" is the Scala/Haskell convention: errors live on the Left, the happy value on the Right, and .map walks the happy path. The Left is held as-is, like a sealed envelope.
A" and works on a single value, a list of values, an optional value, or a result-or-error — without changing a single line. The container's .map handles "is there anything here? how many?" for you.To see the laws working, let's break one on purpose. Here's a "duplicating map" that looks like .map but secretly doubles the list:
def badMap[A,B](xs: List[A], f: A => B): List[B] = xs.map(f) ++ xs.map(f)
It has the right shape — takes a list and a function, returns a list of the right element type. But run it twice in a row vs. once with the composite and you get wildly different sizes:
badMap(badMap(xs, f), g) — first call doubles the list to 2n, second doubles again to 4n.badMap(xs, g compose f) — single call, list stays 2n.Same starting list, same functions, two different answers. The composition law is broken — so badMap isn't a functor, no matter how innocent it looks. (Try it in the law-checker above — flip the use badMap switch.)
.map to be a transparent passthrough that touches contents but never touches structure. Break them and your "container" becomes a tiny rule-bending interpreter — fun, sometimes, but no longer composable.Both languages expose the operation as a single method/function. The signature is the giveaway: a function from A to B becomes a function from F[A] to F[B] — and the F can be anything functor-shaped.
Here's the punchline that gives functors their name. Start with a plain function. A functor doesn't just give you a way to push it through a container — it gives you a brand-new function on the container itself. Watch the arrow get wrapped:
That's the trick. A functor is a recipe that does two things at once: it maps types (Int → List[Int]) and it maps arrows (Int => String → List[Int] => List[String]). And it does the second one in a way that preserves identity and composition — the structure of the category itself. Hence: structure-preserving map between categories, a.k.a. a functor.
You can now lift any function into any functor. That gets us a lot, but here's the next obvious question: what about moving values between containers? How do you turn a List[A] into an Option[A] — for every A, the same way? That's a natural transformation: an arrow not between values, not between types, but between functors themselves.