The Lean Language Reference

12.2. Coercing Between Types🔗

Coercions between types are inserted when the Lean elaborator successfully constructs a term, inferring its type, in a context where a term of some other type was expected. Before signaling an error, the elaborator attempts to insert a coercion from the inferred type to the expected type by synthesizing an instance of CoeT. There are two ways that this might succeed:

  1. There could be a chain of coercions from the inferred type to the expected type through a number of intermediate types. These chained coercions are selected based on the inferred type and the expected type, but not the term being coerced.

  2. There could be a single dependent coercion from the inferred type to the expected type. Dependent coercions take the term being coerced into account as well as the inferred and expected types, but they cannot be chained.

The simplest way to define a non-dependent coercion is by implementing a Coe instance, which is enough to synthesize a CoeT instance. This instance participates in chaining, and may be applied any number of times. The expected type of the expression is used to drive synthesis of Coe instances, rather than the inferred type. For instances that can be used at most once, or instances in which the inferred type should drive synthesis, one of the other coercion classes may be needed.

Defining Coercions

The type Even represents the even natural numbers.

structure Even where number : Nat isEven : number % 2 = 0

A coercion allows even numbers to be used where natural numbers are expected. The coe attribute marks the projection as a coercion so that it can be shown accordingly in proof states and error messages, as described in the section on implementing coercions.

attribute [coe] Even.number instance : Coe Even Nat where coe := Even.number

With this coercion in place, even numbers can be used where natural numbers are expected.

def four : Even := 4, 4 % 2 = 0 All goals completed! 🐙 5#eval (four : Nat) + 1
5

Due to coercion chaining, there is also a coercion from Even to Int formed by chaining the Coe Even Nat instance with the existing coercion from Nat to Int:

-1#eval (four : Int) - 5
-1

Dependent coercions are needed when the specific term being coerced is required in order to determine whether or how to coerce the term: for example, only decidable propositions can be coerced to Bool, so the proposition in question must occur as part of the instance's type so that it can require the Decidable instance. Non-dependent coercions are used whenever all values of the inferred type can be coerced to the target type.

Defining Dependent Coercions

The string "four" can be coerced into the natural number 4 with this instance declaration:

instance : CoeDep String "four" Nat where coe := 4 4#eval ("four" : Nat)
4

Ordinary type errors are produced for other strings:

#eval type mismatch "three" has type String : Type but is expected to have type Nat : Type("three" : Nat)
type mismatch
  "three"
has type
  String : Type
but is expected to have type
  Nat : Type

Non-dependent coercions may be chained: if there is a coercion from α to β and from β to γ, then there is also a coercion from α to γ. The chain should be in the form CoeHead?CoeOut*Coe*CoeTail?, which is to say it may consist of:

  • An optional instance of CoeHead α α', followed by

  • Zero or more instances of CoeOut α' , …, CoeOut α'', followed by

  • Zero or more instances of Coe α'' , …, Coe β', followed by

  • An optional instance of CoeTail β' γ

Most coercions can be implemented as instances of Coe. CoeHead, CoeOut, and CoeTail are needed in certain special situations.

CoeHead and CoeOut instances are chained from the inferred type towards the expected type. In other words, information in the type found for the term is used to resolve a chain of instances. Coe and CoeTail instances are chained from the expected type towards the inferred type, so information in the expected type is used to resolve a chain of instances. If these chains meet in the middle, a coercion has been found. This is reflected in their type signatures: CoeHead and CoeOut use semi-output parameters for the coercion's target, while Coe and CoeTail use semi-output parameters for the coercions' source.

When an instance provides a value for a semi-output parameter, the value is used during instance synthesis. However, if no value is provided, then a value may be assigned by the synthesis algorithm. Consequently, every semi-output parameter should be assigned a type when an instance is selected. This means that CoeOut should be used when the variables that occur in the coercion's output are a subset of those in its input, and Coe should be used when the variables in the input are a subset of those in the output.

CoeOut vs Coe instances

A Truthy value is a value paired with an indication of whether it should be considered to be true or false. A Decision is either yes, no, or maybe, with the latter containing further data for consideration.

structure Truthy (α : Type) where val : α isTrue : Bool inductive Decision (α : Type) where | yes | maybe (val : α) | no

“Truthy” values can be converted to Bools by forgetting the contained value. Bools can be converted to Decisions by discounting the maybe case.

@[coe] def Truthy.toBool : Truthy α Bool := Truthy.isTrue @[coe] def Decision.ofBool : Bool Decision α | true => .yes | false => .no

Truthy.toBool must be a CoeOut instance, because the target of the coercion contains fewer unknown type variables than the source, while Decision.ofBool must be a Coe instance, because the source of the coercion contains fewer variables than the target:

instance : CoeOut (Truthy α) Bool := Truthy.isTrue instance : Coe Bool (Decision α) := Decision.ofBool

With these instances, coercion chaining works:

Decision.yes#eval ({ val := 1, isTrue := true : Truthy Nat } : Decision String)
Decision.yes

Attempting to use the wrong class leads to an error:

instance does not provide concrete values for (semi-)out-params Coe (Truthy ?α) Boolinstance : Coe (Truthy α) Bool := Truthy.isTrue
instance does not provide concrete values for (semi-)out-params
  Coe (Truthy ?α) Bool
🔗type class
CoeHead.{u, v} (α : Sort u)
  (β : semiOutParam (Sort v)) :
  Sort (max (max 1 u) v)

CoeHead α β is for coercions that are applied from left-to-right at most once at beginning of the coercion chain.

Instance Constructor

CoeHead.mk.{u, v}

Methods

coe : αβ

Coerces a value of type α to type β. Accessible by the notation x, or by double type ascription ((x : α) : β).

🔗type class
CoeOut.{u, v} (α : Sort u)
  (β : semiOutParam (Sort v)) :
  Sort (max (max 1 u) v)

CoeOut α β is for coercions that are applied from left-to-right.

Instance Constructor

CoeOut.mk.{u, v}

Methods

coe : αβ

Coerces a value of type α to type β. Accessible by the notation x, or by double type ascription ((x : α) : β).

🔗type class
CoeTail.{u, v} (α : semiOutParam (Sort u))
  (β : Sort v) : Sort (max (max 1 u) v)

CoeTail α β is for coercions that can only appear at the end of a sequence of coercions. That is, α can be further coerced via Coe σ α and CoeHead τ σ instances but β will only be the expected type of the expression.

Instance Constructor

CoeTail.mk.{u, v}

Methods

coe : αβ

Coerces a value of type α to type β. Accessible by the notation x, or by double type ascription ((x : α) : β).

Instances of CoeT can be synthesized when an appropriate chain of instances exists, or when there is a single applicable CoeDep instance.When coercing from Nat to another type, a NatCast instances also suffices. If both exist, then the CoeDep instance takes priority.

🔗type class
CoeT.{u, v} (α : Sort u) :
  α → (β : Sort v) → Sort (max 1 v)

CoeT is the core typeclass which is invoked by Lean to resolve a type error. It can also be triggered explicitly with the notation x or by double type ascription ((x : α) : β).

A CoeT chain has the grammar CoeHead? CoeOut* Coe* CoeTail? | CoeDep.

Instance Constructor

CoeT.mk.{u, v}

Methods

coe : β

The resulting value of type β. The input x : α is a parameter to the type class, so the value of type β may possibly depend on additional typeclasses on x.

Dependent coercions may not be chained. As an alternative to a chain of coercions, a term e of type α can be coerced to β using an instance of CoeDep α e β. Dependent coercions are useful in situations where only some of the values can be coerced; this mechanism is used to coerce only decidable propositions to Bool. They are also useful when the value itself occurs in the coercion's target type.

🔗type class
CoeDep.{u, v} (α : Sort u) :
  α → (β : Sort v) → Sort (max 1 v)

CoeDep α (x : α) β is a typeclass for dependent coercions, that is, the type β can depend on x (or rather, the value of x is available to typeclass search so an instance that relates β to x is allowed).

Dependent coercions do not participate in the transitive chaining process of regular coercions: they must exactly match the type mismatch on both sides.

Instance Constructor

CoeDep.mk.{u, v}

Methods

coe : β

The resulting value of type β. The input x : α is a parameter to the type class, so the value of type β may possibly depend on additional typeclasses on x.

Dependent Coercion

A type of non-empty lists can be defined as a pair of a list and a proof that it is not empty. This type can be coerced to ordinary lists by applying the projection:

structure NonEmptyList (α : Type u) : Type u where contents : List α non_empty : contents [] instance : Coe (NonEmptyList α) (List α) where coe xs := xs.contents

The coercion works as expected:

def oneTwoThree : NonEmptyList Nat := [1, 2, 3], [1, 2, 3][] All goals completed! 🐙 [1, 2, 3, 4]#eval (oneTwoThree : List Nat) ++ [4]

Arbitrary lists cannot, however, be coerced to non-empty lists, because some arbitrarily-chosen lists may indeed be empty:

instance : Coe (List α) (NonEmptyList α) where coe xs := xs, don't know how to synthesize placeholder for argument 'non_empty' context: α : Type u_1 xs : List α ⊢ xs ≠ []_
don't know how to synthesize placeholder for argument 'non_empty'
context:
α : Type u_1
xs : List α
⊢ xs ≠ []

A dependent coercion can restrict the domain of the coercion to only lists that are not empty:

instance : CoeDep (List α) (x :: xs) (NonEmptyList α) where coe := x :: xs, α:Type ?u.31x:αxs:List αx :: xs[] All goals completed! 🐙 { contents := [1, 2, 3], non_empty := _ }#eval ([1, 2, 3] : NonEmptyList Nat)
{ contents := [1, 2, 3], non_empty := _ }

Dependent coercion insertion requires that the term to be coerced syntactically matches the term in the instance header. Lists that are known to be non-empty, but which are not syntactically instances of (· :: ·), cannot be coerced with this instance.

fun xs => let ys := xs ++ [4]; sorry : (xs : List Nat) → ?m.228 xs#check fun (xs : List Nat) => let ys : List Nat := xs ++ [4] type mismatch ys has type List Nat : Type but is expected to have type NonEmptyList Nat : Type(ys : NonEmptyList Nat)

When coercion insertion fails, the original type error is reported:

type mismatch
  ys
has type
  List Nat : Type
but is expected to have type
  NonEmptyList Nat : Type
syntaxCoercions
term ::= ...
    | `↑x` represents a coercion, which converts `x` of type `α` to type `β`, using
typeclasses to resolve a suitable conversion function. You can often leave the
`↑` off entirely, since coercion is triggered implicitly whenever there is a
type error, but in ambiguous cases it can be useful to use `↑` to disambiguate
between e.g. `↑x + ↑y` and `↑(x + y)`.
term

Coercions can be explicitly placed using the prefix operator coeNotation : term`↑x` represents a coercion, which converts `x` of type `α` to type `β`, using typeclasses to resolve a suitable conversion function. You can often leave the `↑` off entirely, since coercion is triggered implicitly whenever there is a type error, but in ambiguous cases it can be useful to use `↑` to disambiguate between e.g. `↑x + ↑y` and `↑(x + y)`. .

Unlike using nested type ascriptions, the coeNotation : term`↑x` represents a coercion, which converts `x` of type `α` to type `β`, using typeclasses to resolve a suitable conversion function. You can often leave the `↑` off entirely, since coercion is triggered implicitly whenever there is a type error, but in ambiguous cases it can be useful to use `↑` to disambiguate between e.g. `↑x + ↑y` and `↑(x + y)`. syntax for placing coercions does not require the involved types to be written explicitly.

Controlling Coercion Insertion

Instance synthesis and coercion insertion interact with one another. Synthesizing an instance may make type information known that later triggers coercion insertion. The specific placement of coercions may matter.

In this definition of sub, the Sub Int instance is synthesized based on the function's return type. This instance requires that the two parameters also be Ints, but they are Nats. Coercions are inserted around each argument to the subtraction operator. This can be seen in the output of Lean.Parser.Command.print : command#print.

def sub (n k : Nat) : Int := n - k def sub : Nat → Nat → Int := fun n k => ↑n - ↑k#print sub
def sub : Nat → Nat → Int :=
fun n k => ↑n - ↑k

Placing the coercion operator outside the subtraction causes the elaborator to attempt to infer a type for the subtraction and then insert a coercion. Because the arguments are both Nats, the Sub Nat instance is selected, leading to the difference being a Nat. The difference is then coerced to an Int.

def sub' (n k : Nat) : Int := (n - k) def sub' : Nat → Nat → Int := fun n k => ↑(n - k)#print sub'

These two functions are not equivalent because subtraction of natural numbers truncates at zero:

-4#eval sub 4 8
-4
0#eval sub' 4 8
0

12.2.1. Implementing Coercions🔗

The appropriate CoeHead, CoeOut, Coe, or CoeTail instance is sufficient to cause a desired coercion to be inserted. However, the implementation of the coercion should be registered as a coercion using the coe attribute. This causes Lean to display uses of the coercion with the coeNotation : term`↑x` represents a coercion, which converts `x` of type `α` to type `β`, using typeclasses to resolve a suitable conversion function. You can often leave the `↑` off entirely, since coercion is triggered implicitly whenever there is a type error, but in ambiguous cases it can be useful to use `↑` to disambiguate between e.g. `↑x + ↑y` and `↑(x + y)`. operator. It also causes the norm_cast tactic to treat the coercion as a cast, rather than as an ordinary function.

attributeCoercion Declarations
attr ::= ...
    | The `@[coe]` attribute on a function (which should also appear in a
`instance : Coe A B := ⟨myFn⟩` declaration) allows the delaborator to show
applications of this function as `↑` when printing expressions.
coe

The @[coe] attribute on a function (which should also appear in a instance : Coe A B := myFn declaration) allows the delaborator to show applications of this function as when printing expressions.

Implementing Coercions

The enum inductive type Weekday represents the days of the week:

inductive Weekday where | mo | tu | we | th | fr | sa | su

As a seven-element type, it contains the same information as Fin 7. There is a bijection:

def Weekday.toFin : Weekday Fin 7 | mo => 0 | tu => 1 | we => 2 | th => 3 | fr => 4 | sa => 5 | su => 6 def Weekday.fromFin : Fin 7 Weekday | 0 => mo | 1 => tu | 2 => we | 3 => th | 4 => fr | 5 => sa | 6 => su

Each type can be coerced to the other:

instance : Coe Weekday (Fin 7) where coe := Weekday.toFin instance : Coe (Fin 7) Weekday where coe := Weekday.fromFin

While this works, instances of the coercions that occur in Lean's output are not presented using the coercion operator, which is what Lean users expect. Instead, the name Weekday.fromFin is used explicitly:

def wednesday : Weekday := (2 : Fin 7) def wednesday : Weekday := Weekday.fromFin 2#print wednesday
def wednesday : Weekday :=
Weekday.fromFin 2

Adding the coe attribute to the definition of a coercion causes it to be displayed using the coercion operator:

attribute [coe] Weekday.fromFin attribute [coe] Weekday.toFin def friday : Weekday := (5 : Fin 7) def friday : Weekday := ↑5#print friday
def friday : Weekday :=
↑5

12.2.2. Coercions from Natural Numbers and Integers🔗

The type classes NatCast and IntCast are special cases of Coe that are used to define a coercion from Nat or Int to some other type that is in some sense canonical. They exist to enable better integration with large libraries of mathematics, such as Mathlib, that make heavy use of coercions to map from the natural numbers or integers to other structures (typically rings). Ideally, the coercion of a natural number or integer into these structures is a simp normal form, because it is a convenient way to denote them.

When the coercion application is expected to be the simp normal form for a type, it is important that all such coercions are definitionally equal in practice. Otherwise, the simp normal form would need to choose a single chained coercion path, but lemmas could accidentally stated using a different path. Because simp's internal index is based on the underlying structure of the term, rather than its presentation in the surface syntax, these differences would cause the lemmas to not be applied where expected. NatCast and IntCast instances, on the other hand, should be defined such that they are always definitionally equal, avoiding the problem. The Lean standard library's instances are arranged such that NatCast or IntCast instances are chosen preferentially over chains of coercion instances during coercion insertion. They can also be used as CoeOut instances, allowing a graceful fallback to coercion chaining when needed.

🔗type class
NatCast.{u} (R : Type u) : Type u

The canonical homomorphism Nat R. In most use cases, the target type will have a (semi)ring structure, and this homomorphism should be a (semi)ring homomorphism.

NatCast and IntCast exist to allow different libraries with their own types that can be notated as natural numbers to have consistent simp normal forms without needing to create coercion simplification sets that are aware of all combinations. Libraries should make it easy to work with NatCast where possible. For instance, in Mathlib there will be such a homomorphism (and thus a NatCast R instance) whenever R is an additive monoid with a 1.

The prototypical example is Int.ofNat.

Instance Constructor

NatCast.mk.{u}

Methods

natCast : NatR

The canonical map Nat R.

🔗def
Nat.cast.{u} {R : Type u} [NatCast R] : NatR

The canonical homomorphism Nat R. In most use cases, the target type will have a (semi)ring structure, and this homomorphism should be a (semi)ring homomorphism.

NatCast and IntCast exist to allow different libraries with their own types that can be notated as natural numbers to have consistent simp normal forms without needing to create coercion simplification sets that are aware of all combinations. Libraries should make it easy to work with NatCast where possible. For instance, in Mathlib there will be such a homomorphism (and thus a NatCast R instance) whenever R is an additive monoid with a 1.

The prototypical example is Int.ofNat.

🔗type class
IntCast.{u} (R : Type u) : Type u

The canonical homomorphism Int R. In most use cases, the target type will have a ring structure, and this homomorphism should be a ring homomorphism.

IntCast and NatCast exist to allow different libraries with their own types that can be notated as natural numbers to have consistent simp normal forms without needing to create coercion simplification sets that are aware of all combinations. Libraries should make it easy to work with IntCast where possible. For instance, in Mathlib there will be such a homomorphism (and thus an IntCast R instance) whenever R is an additive group with a 1.

Instance Constructor

IntCast.mk.{u}

Methods

intCast : IntR

The canonical map Int R.

🔗def
Int.cast.{u} {R : Type u} [IntCast R] : IntR

The canonical homomorphism Int R. In most use cases, the target type will have a ring structure, and this homomorphism should be a ring homomorphism.

IntCast and NatCast exist to allow different libraries with their own types that can be notated as natural numbers to have consistent simp normal forms without needing to create coercion simplification sets that are aware of all combinations. Libraries should make it easy to work with IntCast where possible. For instance, in Mathlib there will be such a homomorphism (and thus an IntCast R instance) whenever R is an additive group with a 1.