The Lean Language Reference

18.14. Sum Types🔗

Sum types represent a choice between two types: an element of the sum is an element of one of the other types, paired with an indication of which type it came from. Sums are also known as disjoint unions, discriminated unions, or tagged unions. The constructors of a sum are also called injections; mathematically, they can be considered as injective functions from each summand to the sum.

There are two varieties of the sum type:

  • Sum is polymorphic over all Type universes, and is never a proposition.

  • PSum is allows the summands to be propositions or types. Unlike Or, the PSum of two propositions is still a type, and non-propositional code can check which injection was used to construct a given value.

Manually-written Lean code almost always uses only Sum, while PSum is used as part of the implementation of proof automation. This is because it imposes problematic constraints that universe level unification cannot solve. In particular, this type is in the universe Sort (max 1 u v), which can cause problems for universe level unification because the equation max 1 u v = ?u + 1 has no solution in level arithmetic. PSum is usually only used in automation that constructs sums of arbitrary types.

🔗inductive type
Sum.{u, v} (α : Type u) (β : Type v) :
  Type (max u v)

The disjoint union of types α and β, ordinarily written α β.

An element of α β is either an a : α wrapped in Sum.inl or a b : β wrapped in Sum.inr. α β is not equivalent to the set-theoretic union of α and β because its values include an indication of which of the two types was chosen. The union of a singleton set with itself contains one element, while Unit Unit contains distinct values inl () and inr ().

Constructors

inl.{u, v} {α : Type u} {β : Type v} (val : α) : αβ

Left injection into the sum type α β.

inr.{u, v} {α : Type u} {β : Type v} (val : β) : αβ

Right injection into the sum type α β.

🔗inductive type
PSum.{u, v} (α : Sort u) (β : Sort v) :
  Sort (max (max 1 u) v)

The disjoint union of arbitrary sorts α β, or α ⊕' β.

It differs from α β in that it allows α and β to have arbitrary sorts Sort u and Sort v, instead of restricting them to Type u and Type v. This means that it can be used in situations where one side is a proposition, like True ⊕' Nat. However, the resulting universe level constraints are often more difficult to solve than those that result from Sum.

Constructors

inl.{u, v} {α : Sort u} {β : Sort v} (val : α) : α ⊕' β

Left injection into the sum type α ⊕' β.

inr.{u, v} {α : Sort u} {β : Sort v} (val : β) : α ⊕' β

Right injection into the sum type α ⊕' β.

18.14.1. Syntax🔗

The names Sum and PSum are rarely written explicitly. Most code uses the corresponding infix operators.

syntaxSum Types
term ::= ...
    | The disjoint union of types `α` and `β`, ordinarily written `α ⊕ β`.

An element of `α ⊕ β` is either an `a : α` wrapped in `Sum.inl` or a `b : β` wrapped in `Sum.inr`.
`α ⊕ β` is not equivalent to the set-theoretic union of `α` and `β` because its values include an
indication of which of the two types was chosen. The union of a singleton set with itself contains
one element, while `Unit ⊕ Unit` contains distinct values `inl ()` and `inr ()`.
term  term

α β is notation for Sum α β.

syntaxPotentially-Propositional Sum Types
term ::= ...
    | The disjoint union of arbitrary sorts `α` `β`, or `α ⊕' β`.

It differs from `α ⊕ β` in that it allows `α` and `β` to have arbitrary sorts `Sort u` and `Sort v`,
instead of restricting them to `Type u` and `Type v`. This means that it can be used in situations
where one side is a proposition, like `True ⊕' Nat`. However, the resulting universe level
constraints are often more difficult to solve than those that result from `Sum`.
term ⊕' term

α ⊕' β is notation for PSum α β.

18.14.2. API Reference🔗

Sum types are primarily used with pattern matching rather than explicit function calls from an API. As such, their primary API is the constructors inl and inr.

18.14.2.1. Case Distinction

🔗def
Sum.isLeft.{u_1, u_2} {α : Type u_1}
  {β : Type u_2} : αβBool

Checks whether a sum is the left injection inl.

🔗def
Sum.isRight.{u_1, u_2} {α : Type u_1}
  {β : Type u_2} : αβBool

Checks whether a sum is the right injection inr.

18.14.2.2. Extracting Values

🔗def
Sum.elim.{u_1, u_2, u_3} {α : Type u_1}
  {β : Type u_2} {γ : Sort u_3} (f : αγ)
  (g : βγ) : αβγ

Case analysis for sums that applies the appropriate function f or g after checking which constructor is present.

🔗def
Sum.getLeft.{u_1, u_2} {α : Type u_1}
  {β : Type u_2} (ab : αβ) :
  ab.isLeft = trueα

Retrieves the contents from a sum known to be inl.

🔗def
Sum.getLeft?.{u_1, u_2} {α : Type u_1}
  {β : Type u_2} : αβOption α

Checks whether a sum is the left injection inl and, if so, retrieves its contents.

🔗def
Sum.getRight.{u_1, u_2} {α : Type u_1}
  {β : Type u_2} (ab : αβ) :
  ab.isRight = trueβ

Retrieves the contents from a sum known to be inr.

🔗def
Sum.getRight?.{u_1, u_2} {α : Type u_1}
  {β : Type u_2} : αβOption β

Checks whether a sum is the right injection inr and, if so, retrieves its contents.

18.14.2.3. Transformations

🔗def
Sum.map.{u_1, u_2, u_3, u_4} {α : Type u_1}
  {α' : Type u_2} {β : Type u_3} {β' : Type u_4}
  (f : αα') (g : ββ') : αβα'β'

Transforms a sum according to functions on each type.

This function maps α β to α' β', sending α to α' and β to β'.

🔗def
Sum.swap.{u_1, u_2} {α : Type u_1}
  {β : Type u_2} : αββα

Swaps the factors of a sum type.

The constructor Sum.inl is replaced with Sum.inr, and vice versa.

18.14.2.4. Inhabited

The Inhabited definitions for Sum and PSum are not registered as instances. This is because there are two separate ways to construct a default value (via inl or inr), and instance synthesis might result in either choice. The result could be situations where two identically-written terms elaborate differently and are not definitionally equal.

Both types have Nonempty instances, for which proof irrelevance makes the choice of inl or inr not matter. This is enough to enable partial functions. For situations that require an Inhabited instance, such as programs that use panic!, the instance can be explicitly used by adding it to the local context with Lean.Parser.Term.have : termhave or Lean.Parser.Term.let : term`let` is used to declare a local definition. Example: ``` let x := 1 let y := x + 1 x + y ``` Since functions are first class citizens in Lean, you can use `let` to declare local functions too. ``` let double := fun x => 2*x double (double 3) ``` For recursive definitions, you should use `let rec`. You can also perform pattern matching using `let`. For example, assume `p` has type `Nat × Nat`, then you can write ``` let (x, y) := p x + y ``` let.

Inhabited Sum Types

In Lean's logic, Lean.Parser.Term.panic : term`panic! msg` formally evaluates to `@Inhabited.default α` if the expected type `α` implements `Inhabited`. At runtime, `msg` and the file position are printed to stderr unless the C function `lean_set_panic_messages(false)` has been executed before. If the C function `lean_set_exit_on_panic(true)` has been executed before, the process is then aborted. panic! is equivalent to the default value specified in its type's Inhabited instance. This means that the type must have such an instance—a Nonempty instance combined with the axiom of choice would render the program non-computable.

Products have the right instance:

example : Nat × String := panic! "Cant' find it"

Sums do not, by default:

example : Nat String := failed to synthesize Inhabited (Nat ⊕ String) Additional diagnostic information may be available using the `set_option diagnostics true` command.panic! "Cant' find it"
failed to synthesize
  Inhabited (Nat ⊕ String)

Additional diagnostic information may be available using the `set_option diagnostics true` command.

The desired instance can be made available to instance synthesis using Lean.Parser.Term.have : termhave:

example : Nat String := have : Inhabited (Nat String) := Sum.inhabitedLeft panic! "Cant' find it"
🔗def
Sum.inhabitedLeft.{u, v} {α : Type u}
  {β : Type v} [Inhabited α] : Inhabited (αβ)

If the left type in a sum is inhabited then the sum is inhabited.

This is not an instance to avoid non-canonical instances when both the left and right types are inhabited.

🔗def
Sum.inhabitedRight.{u, v} {α : Type u}
  {β : Type v} [Inhabited β] : Inhabited (αβ)

If the right type in a sum is inhabited then the sum is inhabited.

This is not an instance to avoid non-canonical instances when both the left and right types are inhabited.

🔗def
PSum.inhabitedLeft.{u_1, u_2} {α : Sort u_1}
  {β : Sort u_2} [Inhabited α] :
  Inhabited (α ⊕' β)

If the left type in a sum is inhabited then the sum is inhabited.

This is not an instance to avoid non-canonical instances when both the left and right types are inhabited.

🔗def
PSum.inhabitedRight.{u_1, u_2} {α : Sort u_1}
  {β : Sort u_2} [Inhabited β] :
  Inhabited (α ⊕' β)

If the right type in a sum is inhabited then the sum is inhabited.

This is not an instance to avoid non-canonical instances when both the left and right types are inhabited.