Category Theory for Programming Language Semantics

This page formalizes category theory concepts for programming language semantics, supporting denotational semantics for languages like Haskell. It covers definitions, theorems, proofs, and examples, with links to implementations in src/CategoryBasics.hs and exercises in exercises/Exercises.md.

Introduction to Category Theory

Category theory provides a unifying language for modeling computational structures. In programming, a category abstracts types and functions, enabling formal semantics for languages like Haskell. The category Hask has Haskell types as objects (e.g., Int, Bool) and functions as morphisms (e.g., length :: String -> Int), with composition defined by function composition.

This document aligns with the goals of Category Theory in Programming Language Semantics:

Categories

A category \(\mathcal{C}\) consists of:

These satisfy:

Example

In Hask, objects are Haskell types (e.g., Int, Bool), and morphisms are functions (e.g., length :: String -> Int). Composition is function composition, e.g., (length . show) :: Int -> Int.

Relevance:

Functors

A functor \(F: \mathcal{C} \to \mathcal{D}\) maps:

Satisfying:

Example

The Maybe functor in Hask maps a type A to Maybe A and a function f :: A -> B to:


fmap f :: Maybe A -> Maybe B
fmap f Nothing = Nothing
fmap f (Just x) = Just (f x)
            

Theorem: Functor Laws

For a functor \(F: \mathcal{C} \to \mathcal{D}\):

  1. \(F(\text{id}_A) = \text{id}_{F(A)}\).
  2. \(F(g \circ f) = F(g) \circ F(f)\).

Proof

Identity: By functor definition, \(F\) preserves identities, so \(F(\text{id}_A) = \text{id}_{F(A)}\).

Composition: For \(f: A \to B\), \(g: B \to C\), functoriality ensures \(F(g \circ f) = F(g) \circ F(f)\).

Relevance:

See src/CategoryBasics.hs for Maybe and List functor implementations.

Natural Transformations

A natural transformation \(\eta: F \to G\) between functors \(F, G: \mathcal{C} \to \mathcal{D}\) is a family of morphisms \(\eta_A: F(A) \to G(A)\) for each \(A \in \mathcal{C}\), such that for any \(f: A \to B\), the following diagram commutes:

graph LR FA["F(A)"] -->|"\eta_A"| GA["G(A)"] FA -->|"F(f)"| FB["F(B)"] GA -->|"G(f)"| GB["G(B)"] FB -->|"\eta_B"| GB

i.e., \(\eta_B \circ F(f) = G(f) \circ \eta_A\).

Example

In Hask, listToMaybe :: [a] -> Maybe a is a natural transformation from the List functor to the Maybe functor:


listToMaybe [] = Nothing
listToMaybe (x:_) = Just x
            

Proof of Naturality

For listToMaybe, verify:


fmap f (listToMaybe xs) == listToMaybe (fmap f xs)
                

Left: fmap f (listToMaybe []) = fmap f Nothing = Nothing.

Right: listToMaybe (fmap f []) = listToMaybe [] = Nothing.

For x:xs, both yield Just (f x).

Relevance:

Monads

A monad on a category \(\mathcal{C}\) is a functor \(T: \mathcal{C} \to \mathcal{C}\) with natural transformations:

Satisfying:

Example

The State monad in Hask models stateful computations:


return x = \s -> (x, s)
(m >>= k) s = let (a, s') = m s in k a s'
            

Theorem: Left Unit Law

For a monad \((T, \eta, \mu)\), \(\mu \circ T\eta = \text{id}_T\).

Proof

For an object \(A \in \mathcal{C}\), we need \(\mu_A \circ T(\eta_A) = \text{id}_{TA}\). The commutative diagram is:

graph LR TA["T(A)"] -->|"T(\eta_A)"| T2A["T^2(A)"] TA -->|"id_T(A)"| TA2["T(A)"] T2A -->|"\eta_A"| TA2

By the monad’s unit axiom, \(\mu_A \circ T(\eta_A) = \text{id}_{TA}\). In Haskell, for State, return and >>= satisfy this law.

Relevance:

See src/CategoryBasics.hs for State monad implementation.

Cartesian Closed Categories

A category \(\mathcal{C}\) is Cartesian closed if it has:

Example

In Hask, the terminal object is (), products are tuples (A, B), and exponentials are function types A -> B.

Relevance:

Yoneda Lemma

Theorem: Yoneda Lemma

For a category \(\mathcal{C}\), object \(A\), and functor \(F: \mathcal{C} \to \mathbf{Set}\), there is a bijection:

\[ \text{Nat}(\text{Hom}_{\mathcal{C}}(A, -), F) \cong F(A). \]

Proof

Define \(\phi: \text{Nat}(\text{Hom}_{\mathcal{C}}(A, -), F) \to F(A)\) by \(\phi(\eta) = \eta_A(\text{id}_A)\). The inverse maps \(x \in F(A)\) to \(\eta_B(f) = F(f)(x)\) for \(f: A \to B\). Naturality ensures the bijection.

Relevance: