import Mathlib.Data.Nat.Basic
import Mathlib.Logic.Basic
import Mathlib.Tactic.Basic
In programming, we often need to compare values for equality. In most languages, this is done with operators like
==
or .equals()
. However, in type theory, equality is a much richer concept. Instead of being
just a boolean operation that returns true or false, equality is itself a type. This means we can talk about proofs of
equality, transformations between equal things, and even equalities between equalities.
Consider these seemingly simple questions: - When are two functions equal? - When are two proofs of the same theorem
equal? - If we have two ways to show that a = b
, are these ways themselves equal?
These questions lead us to identity types, which provide a foundation for answering such questions and form the basis for more advanced concepts like Homotopy Type Theory.
Before looking at identity types, let’s recall the different kinds of equality we’ve encountered:
#eval 2 + 2 -- 4
#eval 4 -- 4
Here, 2 + 2
and 4
are definitionally equal because they compute to the same value.
a = b
in Lean:-- This needs a proof, even though it's "obvious"
example : 2 + 3 = 5 := rfl
Identity types, written as a = b
or Id a b
in type theory, represent the type of
identifications (or paths) between two terms a
and b
of the same type.
In Lean, the identity type is defined inductively (we’ll see the actual definition later), but conceptually it looks like this:
inductive Eq {α : Type u} (a : α) : α → Type u
| refl : Eq a a
This seemingly simple definition has profound implications. Let’s break it down:
α
and elements a b : α
, there is a type a = b
a = a
using refl
(reflexivity)For example:
def proof_2_plus_2 : 2 + 2 = 4 := rfl
#check proof_2_plus_2 -- proof_2_plus_2 : 2 + 2 = 4
Note that proof_2_plus_2
is not just a boolean value - it’s a term of an identity type.
Identity types have several fundamental properties that make them behave like our intuitive notion of equality.
Every term is equal to itself:
def refl_example {α : Type} (a : α) : a = a := rfl
If a = b
, then b = a
:
def symm_example {α : Type} {a b : α} (h : a = b) : b = a := Eq.symm h
If a = b
and b = c
, then a = c
:
def trans_example {α : Type} {a b c : α} (h₁ : a = b) (h₂ : b = c) : a = c :=
Eq.trans h₁ h₂
The most powerful principle for working with identity types is path induction, also known as the J rule. This principle captures the idea that to prove something about all equalities, it suffices to prove it for reflexivity.
The J rule states that to prove a property P of all equalities p : x = y
, it suffices to prove P for all
reflexivity proofs refl : x = x
. In Lean:
def J {α : Type} {x : α}
(P : (y : α) → x = y → Sort u)
(r : P x rfl)
{y : α} (p : x = y) : P y p :=
match p with
| rfl => r
This might look intimidating, but it’s saying: if you want to prove something about any equality, you only need to prove it for the case where the equality is reflexivity, i.e. the most basic case and all other equalities can be reduced to this.
A simpler version of path induction fixes one endpoint:
def based_path_induction
{α : Type} {a : α}
(P : (x : α) → a = x → Prop)
(r : P a rfl)
{b : α} (p : a = b) : P b p :=
match p with
| rfl => r
Here’s how we might use path induction to prove symmetry of equality:
def symm_using_J {α : Type} {a b : α} (p : a = b) : b = a :=
J (fun x q => x = a) rfl p
Transport is a fundamental operation that allows us to move terms between types that are connected by an equality.
Given a type family P : α → Type
, an equality p : a = b
, and a term x : P a
,
we can transport x
along p
to get a term of type P b
:
def transport {α : Type} {P : α → Type} {a b : α} (p : a = b) (x : P a) : P b :=
match p with
| rfl => x
This operation is incredibly powerful and can be used to define many other operations. For example, we can use it to
define vector transport. Given a vector v : Vector α n
and an equality eq : n = m
, we can
transport v
to a vector of length m
:
def vec_transport {α : Type} {n m : Nat} (eq : n = m) (v : Vector α n) : Vector α m :=
transport eq v
One of the most interesting aspects of identity types is that they can be iterated - we can have equalities between equalities!
Given p q : a = b
, we can form the type p = q
of equalities between equalities:
def double_eq {α : Type} {a b : α} (p q : a = b) : Type :=
p = q
These higher identity types give rise to a rich structure where we can talk about paths between paths:
-- A 2-path (path between paths)
def path_between_paths {α : Type} {a b : α} {p q : a = b} (r : p = q) :
p = q := r
Identity types provide the foundation for Homotopy Type Theory (HoTT), where types are viewed as spaces, terms as points, and equalities as paths.
Type Theory | Homotopy Theory |
---|---|
Type | Space |
Term | Point |
Identity | Path |
Higher Identity | Higher Path |
However, to go there we need to introduce more concepts like algebraic geometry, higher inductive types and univalence. We’ll explore these in future chapters.