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.

Type classes are Lean 4’s mechanism for principled ad-hoc polymorphism. A type class names a collection of operations; an instance proves that a specific type implements those operations; and the elaborator automatically inserts the correct instance at every call site. This page covers declaring classes and instances, the standard library’s key classes, instance synthesis rules, priorities, and how to build class hierarchies with extends.

Declaring a Class

A class declaration introduces a new type class as a special structure. Fields become the methods of the class:
class Describable (α : Type u) where
  describe : α → String

class Printable (α : Type u) where
  prettyPrint : α → String
  lineWidth   : Nat := 80     -- field with default value
The automatically generated projection Describable.describe has type [Describable α] → α → String.

Declaring an Instance

An instance declaration provides a concrete implementation of a class for a specific type:
instance : Describable Nat where
  describe n := s!"Nat({n})"

instance : Describable Bool where
  describe
    | true  => "yes"
    | false => "no"

-- Use the class method
def showTwo [Describable α] (a b : α) : String :=
  describe a ++ " and " ++ describe b

#eval showTwo (3 : Nat) 7       -- "Nat(3) and Nat(7)"
#eval showTwo true false         -- "yes and no"

Named Instances

Give an instance a name when you need to refer to it explicitly:
instance instDescribableString : Describable String where
  describe s := s!"String(\"{s}\")"

-- Access the instance explicitly
#eval instDescribableString.describe "hello"   -- "String(\"hello\")"

Key Standard Library Classes

Arithmetic: Add, Sub, Mul, Div, Neg

Defined in Init.Prelude, these power the +, -, *, /, and - (unary) notations:
class Add (α : Type u) where
  add : α → α → α

-- Custom type with Add instance
structure Vec2 where
  x : Float
  y : Float
  deriving Repr

instance : Add Vec2 where
  add a b := { x := a.x + b.x, y := a.y + b.y }

#eval ({ x := 1.0, y := 2.0 } : Vec2) + { x := 3.0, y := 4.0 }
-- { x := 4.0, y := 6.0 }

Repr — Pretty Printing

Repr α provides the repr function used by #eval to display values:
inductive Color where | red | green | blue
  deriving Repr   -- auto-derived

-- Or manual:
instance : Repr Color where
  reprPrec c _ :=
    match c with
    | .red   => "Color.red"
    | .green => "Color.green"
    | .blue  => "Color.blue"

#eval Color.green    -- Color.green

ToString

ToString provides toString, used in string interpolation s!"…{x}…":
instance : ToString Color where
  toString
    | .red   => "red"
    | .green => "green"
    | .blue  => "blue"

#eval s!"My favorite color is {Color.blue}"
-- "My favorite color is blue"

BEq — Boolean Equality

class BEq (α : Type u) where
  beq : α → α → Bool

-- Provides the == operator
instance : BEq Color where
  beq a b :=
    match a, b with
    | .red,   .red   => true
    | .green, .green => true
    | .blue,  .blue  => true
    | _,      _      => false

#eval Color.red == Color.red    -- true
#eval Color.red == Color.blue   -- false

Ord — Three-Way Comparison

class Ord (α : Type u) where
  compare : α → α → Ordering
-- Ordering is: .lt | .eq | .gt

instance : Ord Color where
  compare a b :=
    match a, b with
    | .red,   .red   => .eq
    | .red,   _      => .lt
    | .green, .red   => .gt
    | .green, .green => .eq
    | .green, .blue  => .lt
    | .blue,  .blue  => .eq
    | .blue,  _      => .gt

#eval compare Color.red Color.blue  -- Ordering.lt

Inhabited — Default Values

class Inhabited (α : Sort u) where
  default : α

-- Every Nat has default 0
#eval (default : Nat)     -- 0
-- Every String has default ""
#eval (default : String)  -- ""

-- Your own type
instance : Inhabited Color where
  default := .red

#eval (default : Color)   -- Color.red

Functor and Monad

Functor and Monad enable map and do-notation respectively:
-- Functor: map a function over a container
#eval [1, 2, 3].map (· * 10)          -- [10, 20, 30]
#eval (some 5).map (· + 1)            -- some 6
#eval (none : Option Nat).map (· + 1) -- none

-- Monad: sequential composition with >>= and do-notation
def safeDivide (n d : Nat) : Option Nat :=
  if d == 0 then none else some (n / d)

def computation : Option Nat := do
  let a ← safeDivide 100 5     -- some 20
  let b ← safeDivide a 4       -- some 5
  return b + 1                  -- some 6

#eval computation   -- some 6

Instance Synthesis

When Lean needs to fill in an instance argument [C α], it searches the instance database for declarations matching the goal type. The search is depth-first and can combine instances:
-- Lean automatically combines these instances:
instance instBEqList [BEq α] : BEq (List α) where
  beq xs ys :=
    xs.length == ys.length &&
    (xs.zip ys).all (fun (a, b) => a == b)

-- Works because [BEq Nat] already exists
#eval ([1, 2, 3] : List Nat) == [1, 2, 3]   -- true
#eval ([1, 2, 3] : List Nat) == [1, 2, 4]   -- false

Priorities

When multiple instances could satisfy the same goal, Lean uses priority to pick one. Higher numbers win. The default priority is 1000:
instance (priority := 2000) highPriorityInst : BEq MyType where
  beq _ _ := true   -- always equal (silly example)

instance (priority := 100) lowPriorityInst : BEq MyType where
  beq a b := a.field == b.field

-- highPriorityInst is chosen because 2000 > 100
Standard named priorities:
KeywordValue
high1100
default1000
mid500
low100
instance (priority := low) : Repr MyType where
  reprPrec _ _ := "..."

Default Instances

A @[default_instance] is selected even when the type is not yet fully known. Lean uses this to pick Nat as the default numeric type:
@[default_instance 100]
instance instOfNatNat (n : Nat) : OfNat Nat n where
  ofNat := n

-- So `#eval 42` infers 42 : Nat rather than requiring an annotation
You can add your own default instances:
@[default_instance]
instance : Inhabited MyConfig where
  default := { host := "localhost", port := 8080 }

Class Hierarchies with extends

extends creates a subclass that inherits all parent methods and adds its own:
class Eq' (α : Type u) where
  eq : α → α → Bool

class Ord' (α : Type u) extends Eq' α where
  compare : α → α → Ordering
  -- We can provide a default implementation in terms of compare
  eq a b :=
    match compare a b with
    | .eq => true
    | _   => false
An instance of Ord' automatically provides an instance of Eq':
instance : Ord' Nat where
  compare a b :=
    if a < b then .lt
    else if a > b then .gt
    else .eq

-- This also works because Ord' extends Eq':
#eval Eq'.eq (3 : Nat) 3   -- true

Multiple Inheritance

A class can extend multiple parents:
class Printable' (α : Type u) extends Repr α, ToString α

-- An instance of Printable' must implement both Repr and ToString methods

outParam for Functional Dependencies

Use outParam to indicate that a class parameter is determined by the other parameters. This controls the order in which Lean solves instance goals:
-- HAdd: the output type γ is determined by α and β
class HAdd (α : Type u) (β : Type v) (γ : outParam (Type w)) where
  hAdd : α → β → γ

-- This allows Lean to infer γ once α and β are known:
#check (1 : Nat) + (2 : Nat)   -- Nat (γ is inferred as Nat)

@[simp] on Instance Lemmas

You can mark theorems about class operations with @[simp] to automate proofs:
@[simp]
theorem beq_self_eq_true [BEq α] [ReflBEq α] (a : α) : (a == a) = true :=
  BEq.rfl

-- simp now closes goals involving a == a automatically
example (n : Nat) : (n == n) = true := by simp

Inspecting Instances

Use #check and #print to explore the class hierarchy:
#check @Functor.map
-- Functor.map : {f : Type u_1 → Type u_2} → [inst : Functor f] → (α → β) → f α → f β

#print Monad
-- class Monad (m : Type u_1 → Type u_2) extends Applicative m ...

-- Inspect a specific instance
#check @instBEqList
When writing polymorphic functions, prefer typeclass constraints like [BEq α] over concrete types. This keeps your code reusable and enables the synthesizer to pick the right implementation automatically.

Build docs developers (and LLMs) love