Interactive Theorem Proving in Lean

Lean logo QR code link to these slides

Lecture 3: Dependent types.

GRACE Spring School, Clervaux (Luxembourg). June 1-5, 2026.
Florent Schaffhauser, Heidelberg University.

Lecture 3: Dependent types

Recap from Lecture 2

  • Propositions are defined as types. We can form new propositions from old ones using , , and . The modus ponens rule corresponds to evaluating a function.
  • The logic is not external to our type system. There are no logical values or truth tables. The rules of inference are syntactic rules, not axioms.
  • To prove a theorem, you write a program. If the program type-checks, the theorem is proved.
  • To write proofs in Lean, you can get assistance from the kernel by entering Lean's tactic mode.
Lecture 3: Dependent types

Outline for Lecture 3

  • In this lecture, we will learn how to encode the and quantifiers, so we can state and prove more sophisticated mathematical statements.
  • This involves learning about all the sophisticated kinds of functions that can be declared in a programming language such as Lean.
Lecture 3: Dependent types

Intermediate Tactics

If you would rather use this time to learn about tactics than about dependently-typed functions, here is an additional practice file for you, on intermediate tactics 🙂.

Intermediate tactics

Lecture 3: Dependent types

Plan for Lecture 3

  1. Type families and dependently-typed functions
  2. Existential and universal statements
Lecture 3: Dependent types
Type families and dependently-typed functions

Many kinds of functions

  • So far, we have worked mostly with basic functions, such as square : Nat → Nat or fact : Nat → Nat. These are simply-typed functions, from a type to a type.

  • But in fact, we have also seen type formers, such as Prod : Type → Type → Type or Sum, which takes types as inputs and returns a type. Another common example would be List : Type → Type.

  • For a fixed 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
    
Lecture 3: Dependent types
Type families and dependently-typed functions

Terms that depend on types, types that depend on terms

So far, we have seen:

  • Terms that depend on terms (simply-typed functions).
  • Types that depend on types (type formers).

There should also be:

  • Terms that depend on types (polymorphic functions).
  • Types that depends on terms (type families).
Lecture 3: Dependent types
Type families and dependently-typed functions

Polymorphic functions

  • 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) and so is the expression id (3 : Int).

Lecture 3: Dependent types
Type families and dependently-typed functions

Type families

  • A type family parameterised by X is a function F: X → Type, so indeed types that depend on terms: for all x : X, F x is a type.
  • As an example, you can think of the type family 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}.
  • If you put all of these together, you get a so-called -type, for instance List Int, which you can picture as the sum (or disjoint union) of all the Tuples n.
Lecture 3: Dependent types
Type families and dependently-typed functions

Representation of a -type

A Sigma-type

Lecture 3: Dependent types
Type families and dependently-typed functions

Dependent pairs

  • Given a type family F: X → Type, we can form the associated -type, sometimes denoted by (x : X) × F x, whose terms are called dependent pairs and are of the form ⟨x, t⟩ where x : X and t : F x.
  • For instance ⟨2, [1, -1]⟩ is a term of type (n : Nat) × Tuples n. Another possible notation would be Sigma Tuples, where Tuples : Nat → Type. And the classical type-theoretic notation would be:

  • Dependent pairs generalize usual pairs: if for all x : X, we have F x = Y, then
    (x : X) × F x = X × Y.
Lecture 3: Dependent types
Type families and dependently-typed functions

Formal definition of a -type

Given a type family F: X → Type, the associated -type is an inductive type with a single constructor. The type Sigma F defined in this way can be denoted by (x : X) × F x.

inductive Sigma {X : Type} (F : X → Type) : 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
Lecture 3: Dependent types
Type families and dependently-typed functions

Inductive families

  • Type families can also be declared inductively. Vectors for instance, can be introduced as follows, mimicking the construction for lists.

  • Let X be a type. We consider the type family Vec X : Nat → Type defined inductively as follows:

    inductive Vec (X : Type) : Nat → Type
    | null                                : Vec X 0
    | cons {n :Nat} (x : X) (v : Vec X n) : Vec X (n + 1)
    
  • Here Vec X n represents the type of lists of length n with entries in X:

    • Vec.null is a list of length 0.
    • For all n : Nat, cons x (v : Vec X n) is a list of length n + 1.
Lecture 3: Dependent types
Type families and dependently-typed functions

Dependently-typed functions

  • We can also use type families F : X → Type to define so-called -types (also called types of dependent functions).
  • A dependent function is a function f : (x : X) → F x whose return type depends on the input value.
  • The classical type-theoretic notation for the type of dependent functions (x : X) → F x is

  • This generalizes function types: if for all x : X, we have F x = Y, then
    (x : X) → F x = X → Y.
Lecture 3: Dependent types
Type families and dependently-typed functions

An example: the zero-tuple function

  • Let us define a function zero-tuple : (n : Nat) → Tuples n. We proceed by induction.
  • For n = 0, we want zero-tuple 0 to be the dependent pair ⟨l, p⟩ consisting of the empty list ([] : List Int) and a proof that the empty list has length 0.
  • And for n = k + 1, we want zero-tuple n to be the dependent pair ⟨l, p⟩ consisting of the list (0 :: zero-tuples k) and a proof that this list has length k + 1.
  • Can you implement this in Lean? 😅 Whoever finishes first gets a ☕, on me!
Lecture 3: Dependent types
Type families and dependently-typed functions

Relations between -types and -types

  • A -type comes equipped with a second projection, which is a dependent function, defined by pattern matching.

    def pr₂ {X : Type} {F : X → Type} : (p : Sigma F) → F (pr₁ p)
    | Sigma.mk x t => t
    
  • We can use this projection to establish an equivalence between dependent functions of the form and sections of .

  • This equivalence is induced by the function that sends a dependent function to the function defined by

Lecture 3: Dependent types
Type families and dependently-typed functions

Representation of a -type

A Pi-type

Lecture 3: Dependent types
Type families and dependently-typed functions

Subtypes

  • The formalism of dependent pairs can be used to define subtypes: we just replace the type family F : X → Type by a predicate P : X → Prop.
  • In Lean, the subtype associated to a predicate can be denoted by (x : X) ×' P x or by {x : X // P x}.
  • Its terms are dependent pairs ⟨x, px⟩ where x is an element of X and px is a proof of the proposition P x. This is an important to recall when defining functions out of a subtype.
  • For instance, it is a good exercise to construct a bijection between the subtype
    {L : List X // L.length = n} of List X and the type Vec X n.
Lecture 3: Dependent types
Existential and universal statements
  1. Type families and dependently-typed functions
  2. Existential and universal statements
Lecture 3: Dependent types
Existential and universal statements

Existential statements

  • Given a predicate 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
    
  • This means that, in order to prove that ∃ x : X, P x, you need to construct a term x : X (a witness) and a proof of the proposition P x (the evidence).

  • Note the analogy with subtypes:

    inductive Subtype {X : Type} (P : X → Prop) : Type
    | intro (x : X) (p : P x) : Subtype P
    

    Also note that ∃ x : X, P x is stronger than saying ¬(∀ x : X, ¬(P x)).

Lecture 3: Dependent types
Existential and universal statements

Universal statements

  • Given a predicate 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.

  • This means that, to prove such a statement, you need to construct a function. So you start your proof with fun x ↦ _ (if you are in term mode) or intro x (if you are in tactic mode).

    example :  w : ℂ,  z : ℂ, z ^ 2 = w :=
      by                          --       ⊢ ∀ w : ℂ, ∃ z : ℂ, z ^ 2 = w
        intro w                   -- w : ℂ ⊢ ∃ z : ℂ, z ^ 2 = w
        sorry
    
  • Note that there might be more than one witness z : ℂ for the property z ^ 2 = w, but that piece of data cannot be recovered from the existential statement itself.

Lecture 3: Dependent types
Existential and universal statements

Stationary sequences are convergent

  • It is possible to use usual mathematical notation in Lean. The quantifier symbols unfold to the definitions that we have given before and it is a good exercise to convert the types represented below into Σ-types and Π-types (by definition, Sequence ℝ := ℕ → ℝ).

    def Sequence.isConvergent (s : Sequence ℝ) : Prop :=
       l : ℝ,  ε > 0,  n : ℕ,  m : Nat, m ≥ n → |s m - l| < ε
    
    def Sequence.isStationary (s : Sequence ℝ) : Prop :=
       a : ℝ,  n : ℕ,  m : Nat, m ≥ n → s m = a
    
  • A good exercise is to prove the following implication.

    theorem stationary_implies_convergent : 
       (s : ℕ → ℝ), s.isStationary → s.isConvergent := sorry
    
Lecture 3: Dependent types
Existential and universal statements

Wrap-up and where to go from here

  • Besides simply-typed functions, we can define type formers, polymorphic functions and type families.

  • To a type family F : X → Type, we can associate:

    • The type of dependent pairs (x : X) × F x, which is a -type.
    • The type of dependent functions (x : X) → F x, which is a -type.
  • Given a predicate 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, of type (x : X) → P x.

  • Now we can prove state some theorems 💻 ! Using dependent types, we can actually formalise a remarkable amount of mathematics.

Lecture 3: Dependent types
Existential and universal statements

Practice time

Here is the practice files on intermediate tactics again, as well as a short exercise on convergent sequences. I am happy to answer any questions you may have 😊.

Intermediate tactics Convergent sequences

Thank you for your attention!

Lecture 3: Dependent types