Entry conditions
Use dependent types when you need:
- Types that vary according to a value — a vector whose type includes its length, a matrix whose type includes its dimensions, a proof whose type is the proposition proved.
- A single framework where programs, specifications, and proofs coexist — where writing code and proving theorems are the same activity.
- A formal language strong enough to state and verify mathematical theorems, not just check that functions return the right kind of output.
From simple types to dependent types
The simply typed lambda calculus assigns every term a type, and function types have the form : the input has type , the output has type . The output type does not depend on the input value — a function of type returns a natural number regardless of which natural number it receives.
Dependent types remove this restriction (Martin-Löf, 1982). A dependent function type allows the output type to mention the input value:
Read: “for any of type , a value of type .” The type is a family indexed by elements of — different inputs can produce outputs of different types.
Pi-types (dependent function types)
A Pi-type generalizes the simple function type . When does not depend on , the Pi-type reduces to the ordinary function type. When does depend on , the Pi-type expresses a universal statement: “for every , there is a .”
Under the Curry-Howard correspondence, a Pi-type corresponds to a universal quantification . A term of type is a function that, given any , produces a proof of . Having such a function means having a uniform proof that holds for every element of .
Example. The type of the identity function on every type:
This says: “for any type , a function from to .” The identity function inhabits this type.
Example. The type of a proof that addition is commutative:
A term of this type is a function that takes two natural numbers and returns a proof that their sum is the same in either order.
Sigma-types (dependent pair types)
A Sigma-type generalizes the product type . Its elements are pairs where and — the type of the second component depends on the value of the first.
Under Curry-Howard, a Sigma-type corresponds to an existential quantification . A term of type is a pair: a witness together with a proof that holds.
Example. The type of “a natural number that is even”:
An element is a pair: a specific number and a proof that is even.
Example. The type of “a group”: a carrier set , an operation, an identity element, and proofs of the group axioms — all bundled in nested Sigma-types.
The extended Curry-Howard correspondence
With dependent types, the correspondence between logic and type theory becomes complete:
| Logic | Type Theory |
|---|---|
| (product type) | |
| (sum type / coproduct) | |
| (function type) | |
| (function to empty type) | |
| (truth) | Unit type (one element) |
| (absurdity) | Empty type (no elements) |
| Proof of | Term of type |
| is provable | Type is inhabited |
This correspondence is the foundation of proof assistants like Lean and Agda: to prove a theorem, write a program of the corresponding type (Pierce, 2002). The type checker verifies the proof by checking that the program is well-typed.
Because the type system is based on intuitionistic logic, excluded middle () is not available by default. A proof of must supply a proof of or a proof of — you cannot argue by contradiction unless you explicitly add classical axioms.
Indexed families and universes
Dependent types require a notion of universe — a type whose elements are themselves types. The symbol (or in some systems) is a universe. To avoid paradoxes (Russell’s paradox in type-theoretic form), universes are stratified:
Each contains all types at level and below. This hierarchy is analogous to the cumulative hierarchy of sets in set theory, but built into the type system itself (The Univalent Foundations Program, 2013).
An indexed family is a function from some index type into a universe: . This is a type-valued function — it assigns a type to each element of . Indexed families are what make Pi-types and Sigma-types meaningful: is a type for each .
Dependent pattern matching
In languages with dependent types, pattern matching is more powerful than in simple typed languages. When you match on a constructor, the type checker refines the types of other variables in scope to reflect what the match tells you.
Example. Given a vector type Vec A n (a list of n elements of type A), matching
on the constructor reveals the length:
- Matching
[](empty vector) tells the checker thatn = 0. - Matching
x :: xstells the checker thatn = suc mfor somem, andxs : Vec A m.
This refinement happens automatically: the type system tracks the information gained by each pattern match and uses it to check subsequent code. This is what makes dependent types powerful for proofs — case analysis in a proof corresponds to pattern matching in code, and the type checker ensures you handle every case.
Applications
Dependent types are the foundation of modern proof assistants — Lean, Agda, Coq, and others. They are used to:
- Formalize mathematics: encode algebraic structures (groups, rings, lattices) as types that bundle operations with proofs of their axioms. A Heyting algebra, for instance, becomes a type carrying a carrier, operations, and proofs of the Heyting axioms.
- Verify software: specify program behavior in the type, then type-check to verify that the implementation meets the specification. Dependent types enable specifications that simple types cannot express (e.g., “this sorting function returns a permutation of its input”).
- Construct certified proofs: state a theorem as a type and construct a proof as a term. The type checker verifies correctness mechanically.
Common mistakes
- Confusing types and terms. In dependent type theory, the boundary between types and terms is fluid — types can contain terms — but each expression still occupies a definite level (term, type, universe). Mixing levels produces type errors.
- Forgetting universe levels. A type of all types () leads to paradox. Proof assistants enforce universe stratification, which sometimes requires explicit level annotations.
- Expecting classical reasoning by default. Dependent type theory is intuitionistic.
Proofs by contradiction require importing a classical axiom (e.g.
Classical.emin Lean), which is available but not automatic.
Minimal data
- A type universe (stratified into levels).
- Pi-types for dependent functions.
- Sigma-types for dependent pairs.
- Inductive type definitions (natural numbers, vectors, equality, etc.).
- A type checker that verifies terms against their types.