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.

Inductive types are the backbone of data representation in Lean 4. From a simple Bool with two constructors to a dependent vector indexed by its length, all data types in Lean 4 are either built-in primitives or defined with the inductive keyword. This page explains the full spectrum of inductive definitions, pattern matching, structures, and the deriving clause.

Simple Enumerations

An inductive type with only nullary constructors is a plain enumeration:
inductive Direction where
  | north : Direction
  | south : Direction
  | east  : Direction
  | west  : Direction

def opposite : Direction → Direction
  | .north => .south
  | .south => .north
  | .east  => .west
  | .west  => .east

#eval opposite .north   -- Direction.south
Inside a match or function definition, you can use the anonymous constructor syntax .north (with a leading dot) when Lean can infer the type.

Recursive Types

An inductive type can refer to itself in its constructors:

Nat — Natural Numbers

Nat is defined in Init.Prelude as:
inductive Nat where
  | zero : Nat
  | succ (n : Nat) : Nat
The compiler replaces this with an efficient GMP-backed implementation, but the logical definition is exactly this two-constructor type.

Custom Linked List

inductive MyList (α : Type u) where
  | nil  : MyList α
  | cons : α → MyList α → MyList α

def MyList.length : MyList α → Nat
  | .nil        => 0
  | .cons _ xs  => 1 + xs.length

def MyList.map (f : α → β) : MyList α → MyList β
  | .nil        => .nil
  | .cons x xs  => .cons (f x) (xs.map f)

#eval (MyList.cons 1 (MyList.cons 2 MyList.nil)).length  -- 2

Binary Tree

inductive Tree (α : Type u) where
  | leaf : Tree α
  | node : Tree α → α → Tree α → Tree α

def Tree.size : Tree α → Nat
  | .leaf         => 0
  | .node l _ r  => 1 + l.size + r.size

def Tree.insert [Ord α] (x : α) : Tree α → Tree α
  | .leaf => .node .leaf x .leaf
  | .node l v r =>
    match compare x v with
    | .lt => .node (l.insert x) v r
    | .eq => .node l v r
    | .gt => .node l v (r.insert x)

Parameterized Types

Types can have type parameters:
inductive Either (α β : Type u) where
  | left  : α → Either α β
  | right : β → Either α β

def Either.mapRight (f : β → γ) : Either α β → Either α γ
  | .left a  => .left a
  | .right b => .right (f b)

structure — Single-Constructor Inductives

structure is syntactic sugar for an inductive with exactly one constructor. It automatically generates projection functions for each field:
structure Point where
  x : Float
  y : Float

-- Generated: Point.x : Point → Float, Point.y : Point → Float
-- and constructor: Point.mk : Float → Float → Point

def origin : Point := { x := 0.0, y := 0.0 }
def moveRight (p : Point) (dx : Float) : Point := { p with x := p.x + dx }

#eval origin.x      -- 0.0
#eval (moveRight origin 3.0).x  -- 3.0
Structures support inheritance via extends:
structure Point3D extends Point where
  z : Float

def pt3 : Point3D := { x := 1.0, y := 2.0, z := 3.0 }
#eval pt3.x    -- 1.0  (inherited field)
#eval pt3.z    -- 3.0

Dependent Inductive Types (Indexed Families)

The most powerful form: the return type of each constructor can mention the index values. This encodes invariants in the type itself.

Vector — Length-Indexed Lists

inductive Vector (α : Type u) : Nat → Type u where
  | nil  : Vector α 0
  | cons : α → Vector α n → Vector α (n + 1)

-- The type system tracks the length
def Vector.head : Vector α (n + 1) → α
  | .cons x _ => x

def Vector.zip : Vector α n → Vector β n → Vector (α × β) n
  | .nil,       .nil       => .nil
  | .cons a as, .cons b bs => .cons (a, b) (zip as bs)

-- Length-3 vector
def v : Vector Nat 3 := .cons 1 (.cons 2 (.cons 3 .nil))
#eval v.head    -- 1

Fin — Bounded Natural Numbers

Fin n is the type of natural numbers strictly less than n:
-- Fin is defined as:
-- structure Fin (n : Nat) where
--   val  : Nat
--   isLt : val < n

def lastIndex (n : Nat) (h : 0 < n) : Fin n := ⟨n - 1, Nat.sub_lt h (by omega)⟩

The Recursor

Every inductive type automatically generates a recursor (also called the elimination principle). For Nat, the recursor is:
-- Nat.rec (generated automatically):
-- Nat.rec : {motive : Nat → Sort u}
--         → motive 0
--         → ((n : Nat) → motive n → motive (n + 1))
--         → (n : Nat) → motive n
You rarely use the recursor directly — match expressions and tactics like induction compile down to it. But it is the foundational principle that makes induction work:
-- Proof by induction using the recursor explicitly
theorem zero_add_explicit (n : Nat) : 0 + n = n :=
  Nat.rec
    (motive := fun n => 0 + n = n)
    rfl                              -- base case: 0 + 0 = 0
    (fun n ih => by simp [Nat.add_succ, ih])  -- step case

Pattern Matching

Pattern matching is the standard way to deconstruct inductive values:
inductive Expr where
  | num  : Int    → Expr
  | add  : Expr   → Expr → Expr
  | mul  : Expr   → Expr → Expr
  | neg  : Expr   → Expr

def Expr.eval : Expr → Int
  | .num n     => n
  | .add e1 e2 => eval e1 + eval e2
  | .mul e1 e2 => eval e1 * eval e2
  | .neg e     => -eval e

-- Nested patterns
def Expr.simplify : Expr → Expr
  | .add (.num 0) e     => simplify e          -- 0 + e → e
  | .add e (.num 0)     => simplify e          -- e + 0 → e
  | .mul (.num 0) _     => .num 0              -- 0 * e → 0
  | .mul (.num 1) e     => simplify e          -- 1 * e → e
  | .add e1 e2          => .add (simplify e1) (simplify e2)
  | .mul e1 e2          => .mul (simplify e1) (simplify e2)
  | .neg e              => .neg (simplify e)
  | e                   => e

Guards in Patterns

def classify (n : Int) : String :=
  match n with
  | n if n < 0  => "negative"
  | 0           => "zero"
  | n if n > 0  => "positive"
  | _           => "unreachable"

The deriving Clause

deriving automatically generates typeclass instances for an inductive type:
inductive Color where
  | red
  | green
  | blue
  deriving Repr, BEq, Inhabited, DecidableEq, Ord, Hashable

-- Repr: pretty-print with #eval
#eval Color.red      -- Color.red

-- BEq: == operator
#eval Color.red == Color.blue   -- false

-- Inhabited: provides a default value
#eval (default : Color)   -- Color.red (first constructor)

-- DecidableEq: decidable equality, enables `if c1 = c2 then ...`
#eval if Color.green = Color.green then "same" else "different"  -- "same"

Common deriving Targets

ClassEffect
ReprGenerates #eval display and repr function
BEqGenerates == Boolean equality
InhabitedProvides default (uses the first constructor)
DecidableEqMakes equality decidable (if a = b then …)
OrdGenerates compare : α → α → Ordering
HashableGenerates hash : α → UInt64
ToStringGenerates toString for string interpolation

Deriving for Structures

structure Config where
  host    : String := "localhost"
  port    : Nat    := 8080
  debug   : Bool   := false
  deriving Repr, BEq

def defaultConfig : Config := {}
#eval defaultConfig
-- { host := "localhost", port := 8080, debug := false }

Mutual Inductive Types

mutual
  inductive Even where
    | zero  : Even
    | succOdd : Odd → Even

  inductive Odd where
    | succEven : Even → Odd
end

def Even.toNat : Even → Nat
  | .zero       => 0
  | .succOdd o  => o.toNat + 1

def Odd.toNat : Odd → Nat
  | .succEven e => e.toNat + 1

class as Inductive Sugar

class is syntactic sugar for structure with special instance synthesis behaviour (see Type Classes):
class Printable (α : Type u) where
  print : α → String

instance : Printable Nat where
  print n := s!"Nat({n})"

Build docs developers (and LLMs) love