Cartesian closed category

Functional languages, lambda calculus

Publish at:
  ____    _    ____ _____ _____ ____ ___    _    _   _
 / ___|  / \  |  _ \_   _| ____/ ___|_ _|  / \  | \ | |
| |     / _ \ | |_) || | |  _| \___ \| |  / _ \ |  \| |
| |___ / ___ \|  _ < | | | |___ ___) | | / ___ \| |\  |
 \____/_/   \_\_| \_\|_|_|_____|____/___/_/   \_\_| \_|
 / ___| |   / _ \/ ___|| ____|  _ \
| |   | |  | | | \___ \|  _| | | | |
| |___| |__| |_| |___) | |___| |_| |
 \____|_____\___/|____/|_____|____/ ____  ___ _____ ____
 / ___|  / \|_   _| ____/ ___|/ _ \|  _ \|_ _| ____/ ___|
| |     / _ \ | | |  _|| |  _| | | | |_) || ||  _| \___ \
| |___ / ___ \| | | |__| |_| | |_| |  _ < | || |___ ___) |
 \____/_/   \_\_| |_____\____|\___/|_| \_\___|_____|____/

Categories come in all shapes and sizes. Among the most important and practically useful categories are Cartesian Closed Categories (CCCs), which provide the mathematical foundation for functional programming languages and lambda calculus. While basic categories give us objects and morphisms, Cartesian Closed Categories add crucial structure that allows us to work with products, exponentials, and higher-order functions in a formally rigorous way.

A Cartesian Closed Category extends the basic category structure with three fundamental capabilities that mirror the building blocks of computation:

  1. Terminal Object - A unique unit object that serves as the categorical analogue of the unit type () in programming
  2. Binary Products - Objects that represent pairs A x B, corresponding to tuple types in programming languages
  3. Exponential Objects - Objects that represent function spaces B^A, corresponding to function types A -> B in programming

What makes these categories particularly powerful is that they provide a complete model for simply-typed lambda calculus. Every well-typed lambda expression can be interpreted as a morphism in a Cartesian Closed Category, and conversely, every morphism corresponds to a computable function.

The closed property is what enables higher-order functions - the ability to treat functions as first-class values that can be passed as arguments and returned as results. This closure property ensures that the category contains not just functions between objects, but also functions between function spaces themselves, creating the rich compositional structures.

Formal definition #

A Cartesian Closed Category (CCC) is a category C that has:

  1. Terminal Object 1
  2. Binary Products for all objects
  3. Exponential Objects for all pairs of objects

Terminal Object #

An object 1 in category C is terminal if for every object A in C, there exists exactly one morphism from A to 1:

∀A ∈ Ob(C), ∃! !_A ∈ Hom(A, 1)

The morphism !_A: A -> 1 is called the unique morphism or terminal morphism from A to 1.

Universal Property: The terminal object is the unique (up to isomorphism) object that receives exactly one morphism from every object in the category.

Mathematical Notation #

Formally, a Cartesian Closed Category C satisfies:

∀A, B, X ∈ Ob(C):

  1. Terminal Object: ∃! !_A: A -> 1

  2. Products: ∃! ⟨f, g⟩: X -> A x B such that:

    • π₁ ∘ ⟨f, g⟩ = f
    • π₂ ∘ ⟨f, g⟩ = g
  3. Exponentials: ∃! curry(f): X -> B^A such that:

  • eval ∘ (curry(f) × id_A) = f (β-law)
  • (Uniqueness) If h: X -> B^A and eval ∘ (h × id_A) = f then h = curry(f)

Category Laws #

A Cartesian Closed Category must satisfy several coherence conditions:

Terminal Object Laws:

  • Uniqueness: If f: A -> 1 and g: A -> 1, then f = g

    This law ensures that the terminal object truly acts as a "unit" - there can be only one way to map any object to it. In programming terms, this means there's exactly one value of the unit type (), and every function that returns unit must return that same value.

  • Identity: !₁ = id₁

    The unique morphism from the terminal object to itself is just the identity morphism. This is a consistency condition that ensures the terminal object behaves properly with respect to composition - the "do nothing" function from unit to unit is indeed the identity.

Product Laws:

  • Pairing Identity (η-law for products): ⟨π₁, π₂⟩ = id_{A×B}

    This fundamental law states that if you take a product A x B, extract its components with the projections π₁ and π₂, and then pair them back together with ⟨π₁, π₂⟩, you get back exactly what you started with. In programming terms:

    (fst(pair), snd(pair)) == pair

    This law ensures that products don't lose information - they are lossless containers for their components.

  • Fusion: ⟨f, g⟩ ∘ h = ⟨f ∘ h, g ∘ h⟩

    This law governs how pairing interacts with composition. It says that if you first apply some function h and then pair the results of f and g, that's the same as pairing the compositions f ∘ h and g ∘ h. In code:

    (f(h(x)), g(h(x))) == let y = h(x) in (f(y), g(y))

    This enables important optimizations and reasoning principles about data flow.

Exponential Laws:

  • η-law (Eta): curry(eval) = id_{B^A}

    This law captures the essence of function spaces. The evaluation morphism eval takes a function and an argument and applies the function to the argument. When you curry this evaluation (treating the argument as a parameter), you get back the identity on the function space. In programming: curry(eval) = λf. f, meaning that λf. λx. f(x) is the same as λf. f.

    Functions are completely determined by their behavior on arguments.

  • β-law (Beta): eval ∘ (curry(f) × id_A) = f

    This is the fundamental computation law for exponentials. It says that if you curry a function f and then evaluate it (by pairing with an argument), you get back the original function f. In programming:

    if g = curry(f), then g(a)(b) = f(a,b)

    This law ensures that currying and evaluation are true inverses, making the exponential a proper internal hom.

  • Curry Fusion: curry(f ∘ (g × id_A)) = curry(f) ∘ g

    This law shows how currying distributes over composition. If you have a function f that operates on pairs where the first component has been transformed by g, currying this is the same as first currying f and then composing with g. In programming terms:

    curry(λ(x,y). f(g(x), y)) = λx. curry(f)(g(x))

    This enables powerful reasoning about partial application and function transformation.

Examples of Cartesian Closed Categories #

  • Set (category of sets and functions)
  • Types (the pure, total fragment of typed functional programming languages)

Programming Examples #

The pure, total subcategory of types and functions in a (pure) functional language forms a Cartesian Closed Category. (Effects like partiality, state, or IO generally break CCC structure unless modeled separately.) Here are detailed examples showing how the three fundamental constructions work in practice:

C# (using generics and delegates)

Programming languages naturally form Cartesian Closed Categories:

  1. Terminal Object: Unit types ((), void, Unit) that every type maps to uniquely
  2. Products: Tuples, pairs, records that combine multiple values
  3. Exponentials: Function types that enable higher-order programming
  4. Universal Properties: Currying/uncurrying isomorphisms work perfectly

Formal implementation #

While the previous examples show how CCCs work in practice through ordinary programming, we can also implement the formal categorical structure using Haskell category theory libraries that provide these abstractions. Here's how the same concepts look using the established CCC typeclass:

Hom-sets Connection to Cartesian Closed Categories #

Hom-sets become especially powerful in Cartesian Closed Categories, where they are related to exponential objects through the internal-hom adjunction.

In a CCC, exponentials internalize morphisms.

The fundamental connection is the natural bijection Hom(X × A, B) ≅ Hom(X, B^A).

Specializing to the terminal object gives Hom(A, B) ≅ Hom(1, B^A), so ordinary morphisms A -> B correspond to global elements of the exponential object B^A.

Key Insights #

These examples demonstrate the profound connection between Hom-sets and Cartesian Closed Categories:

  1. Internalization: Exponential objects B^A internalize morphisms through the natural bijection Hom(X × A, B) ≅ Hom(X, B^A)
  2. First-Class Functions: Functions are both morphisms (structure) and objects (data)
  3. Currying Isomorphism: Hom(A × B, C) ≅ Hom(A, C^B) enables partial application and higher-order programming
  4. Function Spaces: Exponential objects represent entire "spaces" of functions that can be manipulated computationally
  5. Natural Transformations: Systematic ways to transform between hom-functors while preserving structure

This connection is what makes functional programming possible - it allows us to treat functions as data while maintaining their computational meaning.

Visualizing Cartesian Closed Categories #


1. TERMINAL OBJECT (1)
   Every object has exactly one morphism to terminal

     A --- !_A ---> 1 <----- !_B ---- B
                    │
                    │ !_C
                    │
                    C

2. BINARY PRODUCTS (A × B)
   Universal property: unique pairing morphism

            f │          │ g
              ▼          ▼
         X -- ⟨f,g⟩ --> A × B
              ╱           │ │
           π₁╱            │ │π₂
           ╱              │ │
          ▼               ▼ ▼
         A               A   B

     Laws: π₁ ∘ ⟨f,g⟩ = f  and  π₂ ∘ ⟨f,g⟩ = g

3. EXPONENTIAL OBJECTS (B^A)
   Function spaces as objects with evaluation morphism

     curry(f) │
              ▼
         X ------------> B^A
         │                │
      f  │              eval
         │                │
         ▼                ▼
         C <--------- B^A × A

     Law: eval ∘ (curry(f) × id_A) = f

4. CURRYING ISOMORPHISM - The Heart of CCCs

     Hom(A × B, C) ≅ Hom(A, C^B)

       f: A×B → C
           ↕ curry/uncurry
       g: A → C^B

     This makes functions first-class!

5. FUNCTION COMPOSITION AND HIGHER-ORDER STRUCTURE

     A -- f --> B --g --> C   ≡   A --- g∘f ---> C

     Composition as exponential object:
     compose: C^B × B^A → C^A

 6. TYPICAL PROGRAMMING CONSTRUCTS

     Terminal:     () or Unit
     Products:     (A, B), tuples, records
     Exponentials: A → B, function types

     map: (A → B) → ([A] → [B])

         f
    A ------> B
    │         │
    │list     │list
    ▼         ▼
   [A] ─map(f)─> [B]

Conclusion #

Cartesian Closed Categories provide the theoretical foundation for functional programming and lambda calculus. Through our exploration, we've seen how three simple requirements—terminal objects, binary products, and exponential objects—create a rich framework that directly corresponds to the core features of modern programming languages.

The power of CCCs lies in their universal properties, which ensure that:

  • Terminal objects provide a canonical "unit" that every other object can map to uniquely
  • Products give us a systematic way to combine data while preserving all information through projections
  • Exponentials elevate functions to first-class status as objects in their own right

CCCs translate directly into the programming constructs we use daily: unit types, tuples, and function types.

The fundamental isomorphism Hom(A × B, C) ≅ Hom(A, C^B) is perhaps the most important insight CCCs offer. This equivalence:

  • Explains why currying "just works" in functional languages
  • Reveals the deep connection between function arguments and function spaces
  • Demonstrates that multi-argument functions are really single-argument functions returning functions

CCCs resolve the apparent paradox of functions in programming: they are simultaneously computational processes (morphisms) and manipulable data (objects). This duality enables:

  • Higher-order programming - functions that operate on other functions
  • Function composition - building complex operations from simple ones
  • Meta-programming - programs that generate and transform other programs
  • Functional abstractions - patterns like map, filter, and fold

Source code #

Reference implementation (opens in a new tab)

References

  1. Cartesian closed category (Wikipedia) (opens in a new tab)
  2. Cartesian closed category (nLab) (opens in a new tab)