7. Data types and recursion
Declaring data types
A data type declaration gives the type's parameters, its universe, and its constructors, each with named arguments:
Data types must be strictly positive: the type being defined may appear in a constructor argument only as the result of a function type, never to the left of an arrow. Without this restriction one could write non-terminating programs:
Bad does not occur strictly positively in the type of argument f of mk
Pattern matching
Functions out of a data type are defined by pattern matching. Clauses can match several arguments, and patterns can be nested; the clauses are tried from top to bottom, _ matches anything, and a variable matches anything and names it:
Kubus compiles the clauses to a tree of case distinctions on one argument at a time, which is what the core language and the kernel understand. On the way it checks that every case is covered:
and that every clause can be reached:
Parameters of a data type are inferred at constructors, and functions can take parameters before the matched arguments:
cons (suc (suc zero)) (cons (suc (suc (suc (suc zero)))) nil) : List Nat
suc (suc zero) : Nat
Structural recursion, and why
A function may call itself only on a structurally smaller argument: a recursive argument of the constructor matched at the root of the case tree. add m n in the clause for suc m is fine. But Kubus rejects a call on anything else, even on a deeper subterm:
termination check failed: recursive call of half on an argument that is not structurally smaller
The restriction keeps the theory normalizing: every term reduces to a normal form, and every closed term of a data type to a constructor. Together with universe stratification and strict positivity, this is what makes type checking decidable and #eval meaningful. The kernel itself knows no recursion at all: it works with eliminators, and the elaborator's case trees translate to them, which is exactly what structural recursion allows. (half can be written with an auxiliary function that returns a pair.)
Proofs by induction
A proof by induction is a recursive function into a family of path types:
The recursive call addZero n is the induction hypothesis, a path from add n zero to n, and \i -> suc (addZero n i) applies suc to it pointwise. (Constructors are not functions on their own: cong suc would need \(m : Nat) -> suc m.)