โ† back to index

Comonads

Bartosz Milewski ยท 2017 ยท Category Theory for Programmers, Ch. 18

Prereqs: ๐Ÿž Ch16 (Adjunctions). 8 min.

A comonad is a value in context. Where a monad lets you put a value in a container (return) but not peek inside, a comonad lets you extract the focused value but not inject one from nothing. extract reads the focus, duplicate shifts attention to every position, and extend applies a local computation everywhere. Spreadsheets, cellular automata, and image kernels are all comonads: a current cell, plus the whole neighborhood.

Monad a m(a) return m(m(a)) m(a) join Comonad w(a) a extract w(a) w(w(a)) duplicate arrows reversed: dual operations

Extract, duplicate, extend

extract (dual of return) pulls the focused value out. duplicate (dual of join) wraps the structure so that every position becomes the focus of its own copy. extend (dual of bind) takes a co-Kleisli arrow w a → b and applies it at every position. They satisfy three laws mirroring the monad laws: extract . duplicate = id, fmap extract . duplicate = id, and duplicate . duplicate = fmap duplicate . duplicate.

Scheme

The Store comonad

The Store comonad pairs a function s → a (a lookup table) with a current position s. Think of a spreadsheet: every cell has a value (the function maps positions to values), and one cell is focused (the current position). extract evaluates the function at the current position. duplicate creates a Store of Stores, where each position's Store is focused on that position. Store arises from the product/exponential adjunction, composed as L∘R instead of R∘L (which gives the State monad).

Scheme

Cellular automaton via extend

A cellular automaton is a co-Kleisli arrow: it reads the neighborhood (the comonadic context) and produces the next value. extend applies this rule everywhere at once. One step of the automaton is one call to extend. Iteration is composition of co-Kleisli arrows. Conway's Game of Life works the same way with a 2D Store. This extract-then-extend loop is the categorical shape of any system that reads its environment and updates: the same pattern appears in jkclosed feedback loops where perception feeds action feeds consolidation.

Scheme

Monad / comonad duality

Every comonad operation is a monad operation with arrows reversed. If a monad arises from an adjunction L ⊢ R as R∘L, the same adjunction gives a comonad as L∘R. The State monad (s → (a, s)) and the Store comonad ((s → a, s)) both come from the product ⊢ exponential adjunction.

Scheme

Notation reference

Blog / Haskell Scheme Meaning
extract :: w a → a(s-extract s) / (store-extract st)Pull the focused value out
duplicate :: w a → w (w a)(s-duplicate s) / (store-duplicate st)Shift focus to every position
extend :: (w a → b) → w a → w b(s-extend f s) / (store-extend g st)Apply co-Kleisli arrow everywhere
Store s a = (s → a, s)(make-store f pos)Lookup function + current position
Stream a = Cons a (Stream a)(make-stream head tail-thunk)Infinite stream focused on head
w a → b(lambda (st) ...)Co-Kleisli arrow
Neighbors

Milewski chapters

Paper pages that use these concepts

Foundations (Wikipedia)

Translation notes

The blog post defines the Comonad typeclass in Haskell with extract, duplicate, and extend, where duplicate and extend have default implementations in terms of each other. This page implements both explicitly for clarity. The Stream comonad uses thunks for laziness, since Scheme is strict. The Store comonad is represented as a two-element list (f pos) rather than a Haskell data type. The blog post discusses Conway's Game of Life as the motivating example for a 2D Store comonad. This page uses Rule 90 (a 1D elementary cellular automaton) to keep the code short while preserving the core insight: a cellular automaton rule is a co-Kleisli arrow, and one generation is one call to extend. The lens connection (a coalgebra a → Store s a is a getter-setter pair) is mentioned in the blog post but omitted here.

Ready for the real thing? Read the blog post. Start with the Stream comonad, then see how Store models a spreadsheet.