Interactive Theorem Proving in Lean

Lean logo QR code link to these slides

Lecture 1: Lean's syntax

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

Lecture 1: Lean's syntax
Introduction to Lean

Organisation of the tutorial

  • Duration: 8 x 1h.
  • Each lecture: 1/2 Presentation, 1/2 Hands-on practice.
  • Practice files are provided.
  • No installation of Lean required.
Lecture 1: Lean's syntax
Introduction to Lean

Outline

  • Goal: to learn how to use a programming language to represent mathematical objects and proofs.
  • Intended audience: mathematicians who may not be familiar with functional programming languages.
  • A useful resource: The Mathlib 4 documentation webpage, hosted on the Leanprover community website. Just google Mathlib GitHub!
Lecture 1: Lean's syntax
Introduction to Lean

The Lean 4 web server

  • If you would rather dive straight into the practice files, here is the first one!

    Lean syntax

  • This file is about Lean's syntax. It is very basic, but might be useful if you have little to no experience in functional programming.

  • It opens in the Lean 4 web server, from which you can download the edited filesa and save your work. Just look for the Save File button in the menu on the top right!

Lecture 1: Lean's syntax
Introduction to Lean

What is Lean?

  • Lean is a programming language and open-source project created by Leonardo De Moura in 2013. The current version is Lean 4. It is not backwards-compatible with Lean 3.
  • It is a declarative, statically-typed programming language with type inference capabilities, like Haskell.
  • As any functional programming language, Lean is characterised by the immutability of states and the absence of side effects.
  • Lean supports dependently-typed functions and inductive types, making it convenient to use as a theorem prover.
Lecture 1: Lean's syntax
Terms and types

Basic Lean syntax

  1. Types and terms
  2. Simple, curried, and higher-order functions
  3. Inductive types
Lecture 1: Lean's syntax
Terms and types

What is a type?

  • Types are given by a list of common rules:

    • In what context can we form a given type?
    • How do we introduce terms of that type?
    • How do we eliminate terms of that type?
    • What happens when we compute?
  • Many mathematical objects commonly represented as sets, such as or , can be represented as types.

  • In a programmimg language such as Lean, we have access to a large collection of basic types such as Nat and Int, but also String, List X, etc.

Lecture 1: Lean's syntax
Terms and types

Well-typed expressions

In Lean, you can find out the type of an expression by using the #check command:

#check 42             -- 42 : Nat
#check "42"           -- "42" : String
#check 1 + 1          -- 1 + 1 : Nat

#check Nat.mul        -- Nat.mul : Nat → Nat → Nat
#check 1 + 1 = 2      -- 1 + 1 = 2 : Prop
#check 1 + 1 > 2      -- 1 + 1 > 2 : Prop

Note that a well-typed equality can be recognised as a well-typed proposition regardless of whether it is "mathematically correct". The expression 2 + 2 = 5, for instance, is well-typed (since it is an equality between two natural numbers). In contrast, certain routinely seen expressions such as 2 * (3 + 1) = 2 * 3 + 2 * 1 = 8 are not well-typed.

Lecture 1: Lean's syntax
Terms and types

Why bother?

It is common sense not to mix quantities that are not related to one another. Here is an example of an ill-typed expression.

An ill-typed sum Image credits: MikeGogulski, CC BY-SA 3.0.

Lecture 1: Lean's syntax
Terms and types

Some milestones in the development of type theory

  • 1912: Bertrand Russell introduces types, hoping to avoid the emergence of paradoxes.
  • 1940: Alonzo Church develops a simply-typed version of his -calculus.
  • 1967: De Bruijn introduces the Automath programming language, in which he effectively equates proof and type inhabitation and writes a type-checking algorithm.
  • 1973: Per Martin-Löf proposes a dependent type theory that can be used as an alternative foundations system for mathematics. This is the underlying logic of Lean.
  • 1989: Thierry Coquand releases the first official version of Coq (now known as Rocq), a type-checker that supports all the constructs of Martin-Löf type theory.
  • 2013: Vladimir Voevodsky and his collaborators publish a treatise on Homotopy Type Theory, which is the basis for the univalent foundations of mathematics.
Lecture 1: Lean's syntax
Terms and types

Structure of an interactive theorem prover

The architecture of an interactive theorem prover can be represented as follows.

Structure of an ITP Image credits: Assia Mahboubi.

  • Human users interact with the proof assistant by writing libraries.
  • The type-checker checks that the code is syntactically correct.
  • Type theory is a convenient choice of underlying logic for functional programming.
Lecture 1: Lean's syntax
Terms and types
  1. Types and terms
  2. Simple, curried, and higher-order functions
  3. Inductive types
Lecture 1: Lean's syntax
Terms and types

Declaring a simply-typed function

The following is Lean code for the function sending a natural number to .

def square : Nat → Nat := 
  fun (n : Nat) ↦ n * n
  • def is the keyword to declare a function, square is our identifier for this function.
  • Nat → Nat is the type signature of the function square.
  • fun (n : Nat) ↦ n * n is the body of the declaration (appearing after :=).

This is accepted by the type-checker because n * n is recognised as a term of type Nat, where * has been previously defined. The type-annotated form of the expression n * n is (Nat.mul : Nat → Nat → Nat) (n : Nat) (n : Nat), which is inferred by the type-checker.

Lecture 1: Lean's syntax
Simple, curried, and higher-order functions

Recursive functions

  • Simply-typed functions f : X → Y are the basic objects of a language like Lean.

  • This includes basic recursive functions, whose implementation can look similar to the usual mathematical definition:

    def fact : Nat → Nat :=
      fun (k : Nat) ↦ match k with
      | 0     =>  1 
      | k + 1 => (k + 1) * fact k
    
    #check fact    -- fact : Nat → Nat
    
    #check fact 5  -- fact 5 : Nat
    
    #eval  fact 5  -- 120
    
Lecture 1: Lean's syntax
Simple, curried, and higher-order functions

Curried functions

  • The natural way to define a function of two variables in Lean is to use curried notation.

  • A function of two variables can be replaced by a function that sends to a function of , namely .

  • In functional programming languages, when we write f : A → B → C, then, in the expression f a b, the term f a is a function from B to C, and it is applied to b.

    def sum : Nat → Nat → Nat := fun x y ↦ x + y
    
    #check sum 3    -- sum 3 : Nat → Nat
    #eval  sum 3 5  -- 8
    

    If we set instead def sum₁ : Nat → Nat → Nat := fun x ↦ (fun y ↦ x + y), then we get sum = sum₁, by reflexivity.

Lecture 1: Lean's syntax
Simple, curried, and higher-order functions

Higher-order functions

  • We can write curried functions with an arbitrary number of variables: if f : A → B → C → D → E, then for all a : A, b : B, c : C, d : D, we have f a b c d : E.

  • This is not the same as f : A → (B → C) → D → E, which takes as arguments a term a : A, a function u : B → C and a term d : D, returning a term of type E.

  • A → B → C → D → E is the same as A → (B → (C → (D → E))).

    def f : Nat → (Nat → ℝ) → ℝ → ℝ := 
    fun (n : Nat) (u : Nat → ℝ) (x : ℝ) ↦ 2 ^ n * u n + x
    
    def v : Nat → ℝ := fun n ↦ 2 * n
    
    #eval f 3 v (-6)  -- 42
    
Lecture 1: Lean's syntax
Inductive types
  1. Types and terms
  2. Simple, curried, and higher-order functions
  3. Inductive types
Lecture 1: Lean's syntax
Inductive types

Inductive types

The most famous inductive type in mathematics is probably the type Nat, whose definition goes back to Giuseppe Peano in 1889. In Lean, it is implemented as follows.

inductive Nat : Type
| zero : Nat
| succ : Nat → Nat

Inductive declarations produce special functions called constructors. In the present case, there are two of them (two introduction rules for terms of type Nat):

  • Nat.zero : Nat (a function whose value is its own name is called an atom, you can view it as a function from the Unit type to Nat, if you prefer).
  • Nat.succ : Nat → Nat (the successor function), saying that for every n : Nat there is a term n.succ : Nat (dot notation for Nat.succ (n : Nat)).
Lecture 1: Lean's syntax
Inductive types

Product types are inductive types

The fact that product types are defined inductively may seem less familiar to mathematicians.

inductive Prod (X Y : Type) : Type
| mk : X → Y → Prod X Y
  • This means that terms of type Prod X Y are introduced via the constructor Prod.mk : X → Y → Prod X Y.
  • In other words, for all x : X and all y : Y, the term Prod.mk x y is of type Prod X Y and this is the only introduction rule for terms of type Prod X Y.
  • In Lean, the type Prod X Y can be denoted by X × Y and its terms by ⟨x, y⟩ (angle brackets or usual brackets).
Lecture 1: Lean's syntax
Inductive types

Functions out of an inductive type

  • We have already seen examples of a function f : Nat → Nat, namely the factorial function. It was defined via primitive recursion. Note that, to define f (n + 1), we may use n (and f n), as was the case in the definition of fact (n + 1) := (n + 1) * fact n.

  • The point here is that recursion is not limited to Nat: every inductive type has an associated recursion principle. For the product, it implies in particular that, in order to define a function f : X × Y → Z, it suffices to define it on the canonical terms Prod.mk x y. In practice, this is done via pattern matching.

    def proj₁ {X Y : Type} : X × Y → X
      fun t ↦ match t with
      | Prod.mk x y => x
    
Lecture 1: Lean's syntax
Inductive types

The sum of two types

  • The sum of two types and is also defined inductively.

  • The inductive type has two constructors because there are two ways to introduce terms of type : they can come either from or from .

  • To eliminate terms of type into , we have to specify the definition of our function on each constructor. This is done by pattern matching (case analysis).

    inductive Sum (X Y : Type) : Type
    | inl : X → Sum X Y
    | inr : Y → Sum X Y
    
    def characteristic_function_of_second_summand {X Y : Type} : X ⊕ Y → Bool
    | Sum.inl x => (false : Bool)
    | Sum.inr y => (true  : Bool)
    
Lecture 1: Lean's syntax

Wrap-up and where to go from here

  • Statically-typed functional programming languages with type inference capabilities such as Lean 4 can be used to formalise mathematics.
  • Type-checking means verifying that our code is syntactically correct.
  • Curried functions, higher-order functions, and inductive types are used in a variety of contexts and for many purposes, in particular to formalise mathematics.
  • To each inductive type there is associated a recursion principle. In practice, we can define functions out of an inductive type by pattern matching on the constructors of an inductive type.
  • Now let us write some Lean code 🎉 🥳 🎊 !
Lecture 1: Lean's syntax

Practice time

Here is the practice file on Lean's syntax again:

Lean syntax

I am happy to answer any questions you may have 😊. Thank you for your attention!

Lecture 1: Lean's syntax

As a matter of fact, the type of booleans is also an inductive type, with two constructors.