โ† 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 with the product bifunctor, these two give you 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. The operation dimap takes two functions: one to pre-process the input (going backwards), one to post-process 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. It shows up everywhere in the automatic derivation of 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. This is why Haskell can auto-derive Functor: 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

The blog post 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. The automatic derivation of functor instances for ADTs, which Milewski shows via Const and Identity composition, is demonstrated here by manually following the same decomposition for Maybe and Tree. The blog post also discusses the Writer functor's derivation from Kleisli composition. That's covered in Chapter 4. One subtlety from the blog post worth noting: separate functoriality in each argument is not enough to prove joint functoriality. The commutativity condition (applying first then second equals applying second then first) is required. 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.