โ† back to index

Functoriality

Bartosz Milewski ยท 2015 ยท Category Theory for Programmers, Ch. 8

Prereqs: ๐Ÿž Ch7 (Functors). 8 min.

Every algebraic data type you build from products, coproducts, and function types is automatically functorial. The functor instance writes itself because bimap distributes over sums and products, contramap handles the input side of functions, and dimap handles both sides at once.

C D × F E bifunctor: two inputs, one output
covariant: a b f F(a) F(b) contravariant: a b f F(a) F(b) arrow reversed

Bifunctors: functors of two arguments

A bifunctor maps two categories into a third. In programming, that means a type constructor with two type parameters, where you can map over both independently. The canonical example: the pair type (a, b). Apply one function to the first component, another to the second.

Scheme

Coproduct as a bifunctor

The coproduct (tagged union / Either) is also a bifunctor. bimap applies one function to the Left case, the other to the Right case. Together, these two are the building blocks for all algebraic data types.

Scheme

Maybe from bifunctors: algebraic data types are functorial

Maybe a = Either () a. The () is a constant (the Const functor), and a is the identity functor. Since Either is a bifunctor and both Const and Identity are functors, the composite is automatically a functor. Every ADT built from sums and products inherits its functor instance for free.

Scheme

Contravariant functors: reversing the arrows

A contravariant functor is a functor from the opposite category. Instead of fmap : (a -> b) -> F a -> F b, you get contramap : (b -> a) -> F a -> F b. The arrows reverse. The canonical example: predicates. If you have a predicate on strings and a function int -> string, you can build a predicate on ints by converting first, then testing.

Scheme

Comparators: another contravariant functor

Comparators are contravariant too. If you can compare strings, and you have a function person -> string that extracts a name, you can compare people by name. You pull back the comparison through the extraction function.

Scheme

Profunctors: contra in first, co in second

The function arrow a -> b is contravariant in a (the input) and covariant in b (the output). A type that behaves this way is a profunctor. dimap takes two functions: one to adapt the input (going backwards), one to adapt the output (going forwards).

Scheme

The Const functor

The Const functor ignores its second type parameter. Const c a always holds a c, regardless of a. Its fmap does nothing: the value doesn't depend on the type parameter, so there's nothing to transform. Const is the "zero" in the algebra of functors, and the key ingredient when auto-deriving functor instances for ADTs.

Scheme

Putting it together: the functoriality of ADTs

Every algebraic data type is a nested composition of sums (Either), products (pairs), Const, and Identity. Since all of these are functorial, their compositions are too. Haskell can auto-derive Functor because the compiler walks the type structure and assembles fmap from bimap, contramap, and the identity. The only catch: the type parameter must appear in covariant (positive) position. If it appears in a function argument (negative position), you need Contravariant or Profunctor instead.

Scheme

Notation reference

Blog / Haskell Scheme Meaning
bimap f g(bimap-pair f g p)Map both components of a bifunctor
first f = bimap f id(bimap-pair f (lambda (x) x) p)Map only the first component
second g = bimap id g(bimap-pair (lambda (x) x) g p)Map only the second component
contramap f(contramap-pred f pred)Pre-compose: reverse the input arrow
dimap f g h(dimap f g h)Pre-compose f, post-compose g
Const c a(make-const c)Ignores type parameter a
Identity aaPasses type parameter through
Neighbors

Milewski chapters

Paper pages that use these concepts

Foundations (Wikipedia)

Translation notes

Milewski uses Haskell typeclasses (Bifunctor, Contravariant, Profunctor) and C++ templates. This page uses plain Scheme functions, since Scheme has no type system to enforce the laws. Where the blog derives functor instances for ADTs via Const and Identity composition, this page follows the same decomposition manually for Maybe and Tree. Writer's derivation from Kleisli composition is covered in Chapter 4. A subtlety worth flagging: separate functoriality in each argument does not prove joint functoriality. You also need the commutativity condition (applying first then second equals applying second then first). Categories that violate this are called premonoidal.

Ready for the real thing? Read the blog post. Start with the bifunctor section, then trace how Maybe decomposes into Const + Identity.