Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/leanprover/lean4/llms.txt

Use this file to discover all available pages before exploring further.

In Lean 4, every expression has a type and types are themselves first-class expressions. The same core language handles both programs and proofs, because a proof of proposition P is simply a term of type P. This page surveys the type hierarchy, built-in concrete types, compound types, and the expression forms you will use constantly when writing Lean code.

The Universe Hierarchy

Types are organized in a hierarchy of universes:
UniverseAlso written asContains
Sort 0PropPropositions (proof-irrelevant)
Sort 1Type or Type 0Ordinary data types
Sort 2Type 1Types of types
Sort uType (u - 1)The u-th universe
#check Prop        -- Prop : Type 1  (Sort 0 is a Type itself)
#check Type        -- Type : Type 1
#check Type 0      -- Type 0 : Type 1
#check Type 1      -- Type 1 : Type 2
#check Sort 0      -- Sort 0 : Sort 1  (Sort 0 = Prop)
Prop is proof-irrelevant: two proofs of the same proposition are definitionally equal. This makes it safe to erase proofs during compilation.
-- Prop example
example : 1 + 1 = 2 := rfl   -- rfl : 1 + 1 = 2

-- Type example
#check (Nat : Type)            -- Nat : Type
#check (List Nat : Type)       -- List Nat : Type
Universe polymorphism lets you write one definition that works at any level. Use universe u to declare a universe variable, then write Type u in your signature.

Built-in Scalar Types

Nat — Natural Numbers

Nat is the type of non-negative integers (0, 1, 2, …). It is defined as an inductive type in Init.Prelude and given an efficient arbitrary-precision implementation by the compiler:
-- Subtraction on Nat saturates at 0
#eval (5 : Nat) - 3    -- 2
#eval (3 : Nat) - 5    -- 0

-- Nat literals
def x : Nat := 1_000_000

Int — Integers

#eval (3 : Int) - 5    -- -2
#eval Int.natAbs (-7)  -- 7

Bool — Booleans

-- Two constructors: Bool.true, Bool.false (exported as true and false)
#eval true && false    -- false
#eval !true            -- false
#eval (true : Bool)    -- true

Char — Unicode Characters

#eval 'A'.toNat        -- 65
#eval Char.isAlpha 'z' -- true

String — UTF-8 Strings

#eval "hello".length   -- 5
#eval "hello " ++ "world"   -- "hello world"
#eval s!"Result: {1 + 1}"   -- "Result: 2"  (string interpolation)

Float — Double-Precision Floating Point

#eval (3.141592653589793 : Float)   -- 3.141592653589793
#eval Float.sqrt 2.0                -- 1.4142135623730951
#eval (2.5 : Float) + 1.0           -- 3.5

Fixed-Width Integer Types

Lean provides UInt8, UInt16, UInt32, UInt64, USize (unsigned) and Int8, Int16, Int32, Int64 (signed):
#eval (255 : UInt8) + 1    -- 0  (wraps around)
#eval (127 : Int8)         -- 127

Product Types (Tuples)

The product type α × β (written Prod α β) pairs two values. Tuples are right-nested:
-- Pair
def p : Nat × String := (42, "hello")
#eval p.1    -- 42
#eval p.2    -- "hello"

-- Triple (right-nested pair)
def t : Nat × String × Bool := (1, "yes", true)
#eval t.1       -- 1
#eval t.2.1     -- "yes"
#eval t.2.2     -- true
You can also use anonymous constructors with angle brackets:
def p2 : Nat × String := ⟨42, "hello"

Sum Types

The sum type α ⊕ β (written Sum α β) holds either a value of type α or one of type β:
def left  : Sum Nat String := Sum.inl 42
def right : Sum Nat String := Sum.inr "hello"

def describe (x : Sum Nat String) : String :=
  match x with
  | .inl n => s!"Number: {n}"
  | .inr s => s!"String: {s}"

#eval describe left   -- "Number: 42"
#eval describe right  -- "String: hello"

Option — Optional Values

Option α is either some a (containing a value a : α) or none (no value):
def safeDivide (n d : Nat) : Option Nat :=
  if d == 0 then none else some (n / d)

#eval safeDivide 10 2   -- some 5
#eval safeDivide 10 0   -- none

-- Pattern matching
def showResult (x : Option Nat) : String :=
  match x with
  | some n => s!"Got {n}"
  | none   => "Nothing"

List — Linked Lists

List α is the standard singly-linked list:
def nums : List Nat := [1, 2, 3, 4, 5]

#eval nums.length       -- 5
#eval nums.head?        -- some 1
#eval nums.tail         -- [2, 3, 4, 5]
#eval nums.map (· * 2) -- [2, 4, 6, 8, 10]
#eval nums.filter (· % 2 == 0)  -- [2, 4]
#eval nums.foldl (· + ·) 0      -- 15

Array — Dynamic Arrays

Array α provides O(1) random access and amortised O(1) push/pop, unlike the linked List:
def arr : Array Nat := #[10, 20, 30, 40]

#eval arr.size          -- 4
#eval arr[0]!           -- 10  (panics if out of range)
#eval arr[2]?           -- some 30
#eval arr.push 50       -- #[10, 20, 30, 40, 50]
#eval arr.map (· + 1)   -- #[11, 21, 31, 41]

-- Safe indexed access with proof
example : arr[1]'(by simp [arr]) = 20 := rfl
Prefer Array over List when you need random access or are appending many elements. Prefer List when you need structural recursion with easy pattern matching on the head.

Type Ascription

Ascription forces an expression to have a particular type:
#check (42 : Int)       -- 42 : Int  (instead of the default Nat)
#check ([] : List Int)  -- [] : List Int

def x := (0 : Float)    -- explicitly Float, not Nat

Let Expressions

def example1 : Nat :=
  let a := 10
  let b := a * 2
  a + b            -- 30

-- let with type annotation
def example2 : String :=
  let n : Nat := 7
  let s : String := s!"n = {n}"
  s
let mut creates a mutable local variable (only inside do blocks):
def sumList (xs : List Nat) : IO Nat := do
  let mut total := 0
  for x in xs do
    total := total + x
  return total

where Clauses

where defines local helper definitions scoped to the enclosing declaration:
def fib (n : Nat) : Nat :=
  go n 0 1
where
  go : Nat → Nat → Nat → Nat
    | 0,     a, _ => a
    | n + 1, a, b => go n b (a + b)

#eval fib 10    -- 55

Dependent Types

In Lean 4, types can depend on values. The canonical example is Vector α n, a list of exactly n elements:
-- A type whose second argument is a Nat value
inductive Vector (α : Type u) : Nat → Type u where
  | nil  : Vector α 0
  | cons : α → Vector α n → Vector α (n + 1)

def myVec : Vector Nat 3 :=
  .cons 1 (.cons 2 (.cons 3 .nil))

-- The type encodes the length: you cannot confuse a length-3 with a length-2 vector

if/else Expressions

def abs (n : Int) : Int :=
  if n ≥ 0 then n else -n

-- Dependent if: the branches can use the condition as a proof
def safeHead (xs : List Nat) : Option Nat :=
  if h : xs.length > 0 then
    some (xs.get ⟨0, h⟩)
  else
    none

match Expressions

def describeList (xs : List Nat) : String :=
  match xs with
  | []       => "empty"
  | [x]      => s!"singleton {x}"
  | x :: _ => s!"starts with {x}"

-- Nested patterns
def sumPairs (ps : List (Nat × Nat)) : Nat :=
  match ps with
  | []            => 0
  | (a, b) :: rest => a + b + sumPairs rest

Build docs developers (and LLMs) love