4.1 KiB
What makes dependent type theory dependent?
The short explanation is that what makes dependent type theory dependent is that types can depend on parameters.
You have already seen a nice example of this: the type List α depends on the argument α, and
this dependence is what distinguishes List Nat and List Bool.
For another example, consider the type Vector α n, the type of vectors of elements of α of length n.
This type depends on two parameters: the type α : Type of the elements in the vector and the length n : Nat.
Suppose we wish to write a function cons which inserts a new element at the head of a list.
What type should cons have? Such a function is polymorphic: we expect the cons function for Nat, Bool,
or an arbitrary type α to behave the same way.
So it makes sense to take the type to be the first argument to cons, so that for any type, α, cons α
is the insertion function for lists of type α. In other words, for every α, cons α is the function that takes an element a : α
and a list as : List α, and returns a new list, so we have cons α a as : list α.
It is clear that cons α should have type α → List α → List α. But what type should cons have?
A first guess might be Type → α → list α → list α, but, on reflection, this does not make sense:
the α in this expression does not refer to anything, whereas it should refer to the argument of type Type.
In other words, assuming α : Type is the first argument to the function, the type of the next two elements are α and List α.
These types vary depending on the first argument, α.
This is an instance of a dependent function type, or dependent arrow type. Given α : Type and β : α → Type,
think of β as a family of types over α, that is, a type β a for each a : α.
In that case, the type (a : α) → β a denotes the type of functions f with the property that,
for each a : α, f a is an element of β a. In other words, the type of the value returned by f depends on its input.
Notice that (a : α) → β makes sense for any expression β : Type. When the value of β depends on a
(as does, for example, the expression β a in the previous paragraph), (a : α) → β denotes a dependent function type.
When β doesn't depend on a, (a : α) → β is no different from the type α → β.
Indeed, in dependent type theory (and in Lean), α → β is just notation for (a : α) → β when β does not depend on a.
Returning to the example of lists, we can use the command #check to inspect the type of the following List functions
We will explain the @ symbol and the difference between the round and curly braces momentarily.
#check @List.cons -- {α : Type u_1} → α → List α → List α
#check @List.nil -- {α : Type u_1} → List α
#check @List.length -- {α : Type u_1} → List α → Nat
#check @List.append -- {α : Type u_1} → List α → List α → List α
Just as dependent function types (a : α) → β a generalize the notion of a function type α → β by allowing β to depend on α,
dependent Cartesian product types (a : α) × β a generalize the Cartesian product α × β in the same way. Dependent products are also
called sigma types, and you can also write them as Σ a : α, β a. You can use ⟨a, b⟩ or Sigma.mk a b to create a dependent pair.
universe u v
def f (α : Type u) (β : α → Type v) (a : α) (b : β a) : (a : α) × β a :=
⟨a, b⟩
def g (α : Type u) (β : α → Type v) (a : α) (b : β a) : Σ a : α, β a :=
Sigma.mk a b
#reduce f
#reduce g
#reduce f Type (fun α => α) Nat 10
#reduce g Type (fun α => α) Nat 10
#reduce (f Type (fun α => α) Nat 10).1 -- Nat
#reduce (g Type (fun α => α) Nat 10).1 -- Nat
#reduce (f Type (fun α => α) Nat 10).2 -- 10
#reduce (g Type (fun α => α) Nat 10).2 -- 10
The function f and g above denote the same function.