Dependent Types
Definition
A type system is called dependent when types can depend on values. This means that the shape, constraints, or meaning of a type may be parameterized by program values. With dependent types, you can express rich invariants and relationships directly in the type system.
Examples
Vec<n, T>- a vector type indexed by its lengthnMatrix<rows, cols, T>- dimensions encoded in the typeProof<p>- a type representing a proof of a propositionp
For instance, in a language with dependent types:
append : Vec<n, T> -> Vec<m, T> -> Vec<n + m, T>
The type says that appending two vectors produces a vector whose length is the sum of the lengths.
Purpose
- Capture invariants in the type system so they are checked at compile time
- Reduce or eliminate certain classes of runtime errors
- Allow programs to carry proofs of their own correctness
- Enable more expressive APIs where type signatures encode precise behavior
Key Characteristics
- Types are computed: Types can be expressions evaluated from program values
- Terms appear in types: No strict separation between values and types
- Type checking may evaluate code: The compiler may need to run computations to verify type constraints
- Expressiveness: Can model proofs, exact sizes, state transitions, or logical conditions
Dependent Function Types
A dependent function type is written (in type theory notation) as:
Π x : A. B(x)
This means: for each value x of type A, the result type is B(x) which may depend on the value of x.
In programming terms:
-
Non-dependent function:
f : A -> BThe output type
Bis fixed regardless of the input value. -
Dependent function:
f : (x : A) -> B(x)The output type varies based on the actual input
x.
In Practice
-
Fully supported in Agda, Idris, Coq, Lean
-
Rust supports restricted forms via const generics and trait bounds
-
Commonly used for:
- Exact-size arrays and matrices
- State machines with type-level states
- Encoding logical proofs alongside code