If you prefer to use this time to learn more advanced tactics instead, here is a practice file:
square : ℕ → ℕ
or fact : ℕ → ℕ
. These are simply-typed functions, from a type to a type.Prod : Type → Type → Type
or Sum
, which takes types as inputs and returns a type. Another common example would be List : Type → Type
.X
, the type List X
is defined inductively: you start with the empty list and then prepend terms of type X
.inductive List (X : Type) : Type
| nil : List X
| cons : X → List X → List X
#check @List -- List : Type → Type
So far, we have seen:
There should be:
A polymorphic function is a function that takes a type as an argument. The simplest polymorphic function is the identity function:
def id {X : Type} : X → X := fun x ↦ x
#check id -- id {X : Type} : X → X
id
is indeed a term that depends (implicitely) on a type. For instance, id (X := Nat)
(also denoted by @id Nat
) is a term of type Nat → Nat
, i.e. a function from Nat
to Nat
.id
is an example of an overloaded term: whatever the type X
, we may denote its identity function simply by id
, so an expression such as id (3 : Nat)
is well-typed (and of type Nat
).X
is a function F: X → Type
, so indeed types that depend on terms: for all x : X
, F x
is a type.Tuples : Nat → Type
such that Tuples n
is the type of lists of integers of length n
:Tuples n := {L : List Int // List.length L = n}
.List Int
, which you can picture as the sum of all the Tuples n
.F: X → Type
, we can form the associated (x : X) × F x
, whose terms are called dependent pairs and are of the form ⟨x, t⟩
where x : X
and t : F x
.⟨2, [1, -1]⟩
is a term of type (n : Nat) × Tuples n
. Another possible notation would be Sigma Tup
, where Tuples : Nat → Type
. And the classical type-theoretic notation would be:x : X
, we have F x = Y
, then((x : X) × F x) = (X × Y)
.Given a type family F: X → Type
, the associated Sigma F
defined in this way can be denoted by (x : X) × F x
.
inductive Sigma {X : Type} (F : X → Type)
| mk (x : X) (t : F x) : Sigma F
Projection to the first factor can be defined by pattern matching (which under the hood calls upon the induction principle associated to the inductive type Sigma F
):
def pr₁ {X : Type} {F : X → Type} : Sigma F → X
| Sigma.mk x t => x
F : X → Type
to define so-called f : (x : X) → F x
whose return type depends on the input value.(x : X) → F x
isx : X
, we have F x = Y
, then((x : X) → F x) = (X → Y)
.zero-tuple : (n : ℕ) → Tuples n
. We proceed by induction.n = 0
, we set zero-tuple 0 := ([] : List Int)
(the empty list). If zero-tuple n : Tuples n
is defined, we set zero-tuple (n + 1) := (0 :: zero-tuple n)
, which is indeed of type Tuples (n + 1)
.def pr₂ {X : Type} {F : X → Type} : (p : Sigma F) → F (pr₁ p)
| Sigma.mk x t => t
#check
the type of List
that we have defined, you will find that it is of type Type → Type
. To be able to write this, we need to be able to treat Type
as a term. But of what type?Type
is a term of type Type 1
. As a consequence, type formers with values in Type
are also terms of type Type 1
.Prop
is a universe, of type Type
(also denoted by Type 0
). You can think of Type l
as the universe of types of level l
(we will not define this further here).z : ℂ
and consider the proposition z ^ 2 = -1
. Thinking of propositions-as-types, this defines a type family F : ℂ → Prop
.⟨z, p⟩
, where p : z ^ 2 = -1
(so p
is a proof of z ^ 2 = -1
), as a proof that -1
has a square root in ℂ
.P : X → Prop
on a type X
, the proposition ∃ x : X, P x
is the proposition defined inductively as follows.inductive Exists {X : Type} (P : X → Prop) : Prop
| intro (x : X) (p : P x) : Exists P
∃ x : X, P x
, you need to construct a term x : X
(a witness) and a proof of the proposition P x
(the evidence).¬(∀ x : X, ¬(P x))
is weaker than saying that ∃ x : X, P x
.x : ℝ
and consider the proposition x ^ 2 ≥ 0
. Thinking of propositions-as-types, this defines a type family F : ℝ → Prop
.f : (x : ℝ) → x ^ 2 ≥ 0
sends to a real number x
to a proof of x ^ 2 ≥ 0
. So we can interpret such a dependent function f
as a proof of the proposition ∀ x : ℝ, x ^ 2 ≥ 0
.P : X → Prop
on a type X
, the proposition ∀ x : X, P x
is the proposition defined by the type of dependent functions (x : X) → P x
.fun x ↦ _
(if you are in term mode) or intro x
(if you are in tactic mode).∀ w : ℂ, ∃ z : ℂ, z ^ 2 = w
, then intro w
will change your goal to ∃ z : ℂ, z ^ 2 = w
, for a w
that is now fixed. Then you have to construct a square root of w
to conclude.z : ℂ
for the property z ^ 2 = w
, but this piece of data cannot be recovered from the existential statement itself!(a b : A) → a = b
, meaning that to any two arbitrary terms a
and b
of type A
, there is associated an identification a = b
.A := (x = y)
for x, y
in some type X
are all propositions. The detailed study of identity types is a fundamental topic in homotopy type theory. For instance, it can be proven that, for all type A
, the type IsProp A
is a proposition .theorem FLT {n : Nat} {x y z : Int} : (n > 2) → x ^ n + y ^ n = z ^ n → x * y * z = 0
FLT
type introduced above is a proposition .F : X → Type
, we define an associated (x : X) × (F x)
) and an associated (x : X) → F x
).P : X → Prop
, the proposition ∃ x : X, P x
is the proposition whose proofs are constructed using dependent pairs (x : X) ×' (P x)
. And a proof of the proposition ∀ x : X, P x
is a dependent function (x : X) → P x
.Still missing: the fantastic drawing about Sigma-types and Pi-types.