5.6. Coercions🔗

When the Lean elaborator is expecting one type but produces a term with a different type, it attempts to automatically insert a coercion, which is a specially designated function from the term's type to the expected type. Coercions make it possible to use specific types to represent data while interacting with APIs that expect less-informative types. They also allow mathematical developments to follow the usual practice of “punning”, where the same symbol is used to stand for both an algebraic structure and its carrier set, with the precise meaning determined by context.

Lean's standard library and metaprogramming APIs define many coercions. Some examples include:

  • A Nat may be used where an Int is expected.

  • A Fin may be used where a Nat is expected.

  • An α may be used where an Option α is expected. The coercion wraps the value in some.

  • An α may be used where a Thunk α is expected. The coercion wraps the term in a function to delay its evaluation.

  • When one syntax category c1 embeds into another category c2, a coercion from TSyntax c1 to TSyntax c2 performs any necessary wrapping to construct a valid syntax tree.

Coercions are found using type class synthesis. The set of coercions can be extended by adding further instances of the appropriate type classes.

Coercions

All of the following examples rely on coercions:

example (n : Nat) : Int := n example (n : Fin k) : Nat := n example (x : α) : Option α := x def th (f : Int String) (x : Nat) : Thunk String := f x open Lean in example (n : Ident) : Term := n

In the case of th, using Lean.Parser.Command.print : command#print demonstrates that evaluation of the function application is delayed until the thunk's value is requested:

def th : (Int → String) → Nat → Thunk String := fun f x => { fn := fun x_1 => f ↑x }#print th
def th : (Int → String) → Nat → Thunk String :=
fun f x => { fn := fun x_1 => f ↑x }

Coercions are not used to resolve generalized field notation: only the inferred type of the term is considered. However, a type ascription can be used to trigger a coercion to the type that has the desired generalized field. Coercions are also not used to resolve OfNat instances: even though there is a default instance for OfNat Nat, a coercion from Nat to α does not allow natural number literals to be used for α.

Coercions and Generalized Field Notation

The name Nat.bdiv is not defined, but Int.bdiv exists. The coercion from Nat to Int is not considered when looking up the field bdiv:

example (n : Nat) := invalid field 'bdiv', the environment does not contain 'Nat.bdiv' n has type Natn.bdiv 2
invalid field 'bdiv', the environment does not contain 'Nat.bdiv'
  n
has type
  Nat

This is because coercions are only inserted when there is an expected type that differs from an inferred type, and generalized fields are resolved based on the inferred type of the term before the dot. Coercions can be triggered by adding a type ascription, which additionally causes the inferred type of the entire ascription term to be Int, allowing the function Int.bdiv to be found.

example (n : Nat) := (n : Int).bdiv 2
Coercions and OfNat

Bin is an inductive type that represents binary numbers.

inductive Bin where | done | zero : Bin Bin | one : Bin Bin def Bin.toString : Bin String | .done => "" | .one b => b.toString ++ "1" | .zero b => b.toString ++ "0" instance : ToString Bin where toString | .done => "0" | b => Bin.toString b

Binary numbers can be converted to natural numbers by repeatedly applying Bin.succ:

def Bin.succ (b : Bin) : Bin := match b with | .done => Bin.done.one | .zero b => .one b | .one b => .zero b.succ def Bin.ofNat (n : Nat) : Bin := match n with | 0 => .done | n + 1 => (Bin.ofNat n).succ

Even if Bin.ofNat is registered as a coercion, natural number literals cannot be used for Bin:

attribute [coe] Bin.ofNat instance : Coe Nat Bin where coe := Bin.ofNat #eval (failed to synthesize OfNat Bin 9 numerals are polymorphic in Lean, but the numeral `9` cannot be used in a context where the expected type is Bin due to the absence of the instance above Additional diagnostic information may be available using the `set_option diagnostics true` command.9 : Bin)
failed to synthesize
  OfNat Bin 9
numerals are polymorphic in Lean, but the numeral `9` cannot be used in a context where the expected type is
  Bin
due to the absence of the instance above
Additional diagnostic information may be available using the `set_option diagnostics true` command.

This is because coercions are inserted in response to mismatched types, but a failure to synthesize an OfNat instance is not a type mismatch.

The coercion can be used in the definition of the OfNat Bin instance:

instance : OfNat Bin n where ofNat := n 1010#eval (10 : Bin)
1010

Most new coercions can be defined by declaring an instance of the Coe type class and applying the coe attribute to the function that performs the coercion. To enable more control over coercions or to enable them in more contexts, Lean provides further classes that can be implemented, described in the rest of this chapter.

Defining Coercions: Decimal Numbers

Decimal numbers can be defined as arrays of digits.

structure Decimal where digits : Array (Fin 10)

Adding a coercion allows them to be used in contexts that expect Nat, but also contexts that expect any type that Nat can be coerced to.

@[coe] def Decimal.toNat (d : Decimal) : Nat := d.digits.foldl (init := 0) fun n d => n * 10 + d.val instance : Coe Decimal Nat where coe := Decimal.toNat

This can be demonstrated by treating a Decimal as an Int as well as a Nat:

def twoHundredThirteen : Decimal where digits := #[2, 1, 3] def one : Decimal where digits := #[1] -212#eval (one : Int) - (twoHundredThirteen : Nat)
-212
🔗type class
Coe.{u, v} (α : semiOutParam (Sort u))
  (β : Sort v) : Sort (max (max 1 u) v)

Coe α β is the typeclass for coercions from α to β. It can be transitively chained with other Coe instances, and coercion is automatically used when x has type α but it is used in a context where β is expected. You can use the x operator to explicitly trigger coercion.

Instance Constructor

Coe.mk.{u, v}

Methods

coe : αβ

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

5.6.1. Coercion Insertion🔗

The process of searching for a coercion from one type to another is called coercion insertion. Coercion insertion is attempted in the following situations where an error would otherwise occur:

  • The expected type for a term is not equal to the type found for the term.

  • A type or proposition is expected, but the term's type is not a universe.

  • A term is applied as though it were a function, but its type is not a function type.

Coercions are also inserted when they are explicitly requested. Each situation in which coercions may be inserted has a corresponding prefix operator that triggers the appropriate insertion.

Because coercions are inserted automatically, nested type ascriptions provide a way to precisely control the types involved in a coercion. If α and β are not the same type, ((e : α) : β) arranges for e to have type α and then inserts a coercion from α to β.

When a coercion is discovered, the instances used to find it are unfolded and removed from the resulting term. To the extent possible, calls to Coe.coe and related functions do not occur in the final term. This process of unfolding makes terms more readable. Even more importantly, it means that coercions can control the evaluation of the coerced terms by wrapping them in functions.

Controlling Evaluation with Coercions

The structure Later represents a term that can be evaluated in the future, by calling the contained function.

structure Later (α : Type u) where get : Unit α

A coercion from any value to a later value is performed by creating a function that wraps it.

instance : CoeTail α (Later α) where coe x := { get := fun () => x }

However, if coercion insertion resulted in an application of CoeTail.coe, then this coercion would not have the desired effect at runtime, because the coerced value would be evaluated and then saved in the function's closure. Because coercion implementations are unfolded, this instance is nonetheless useful.

def tomorrow : Later String := (Nat.fold 10000 (init := "") (fun _ _ s => s ++ "tomorrow") : String)

Printing the resulting definition shows that the computation is inside the function's body:

def tomorrow : Later String := { get := fun x => Nat.fold 10000 (fun x x s => s ++ "tomorrow") "" }#print tomorrow
def tomorrow : Later String :=
{ get := fun x => Nat.fold 10000 (fun x x s => s ++ "tomorrow") "" }
Duplicate Evaluation in Coercions

Because the contents of Coe instances are unfolded during coercion insertion, coercions that use their argument more than once should be careful to ensure that evaluation occurs just once. This can be done by using a helper function that is not part of the instance, or by using 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 to evaluate the coerced term and then re-use its resulting value.

The structure Twice requires that both fields have the same value:

structure Twice (α : Type u) where first : α second : α first_eq_second : first = second

One way to define a coercion from α to Twice α is with a helper function twice. The coe attribute marks it as a coercion so it can be shown correctly in proof goals and error messages.

@[coe] def twice (x : α) : Twice α where first := x second := x first_eq_second := rfl instance : Coe α (Twice α) := twice

When the Coe instance is unfolded, the call to twice remains, which causes its argument to be evaluated before the body of the function is executed. As a result, the Lean.Parser.Term.dbgTrace : term`dbg_trace e; body` evaluates to `body` and prints `e` (which can be an interpolated string literal) to stderr. It should only be used for debugging. dbg_trace executes just once:

{ first := 5, second := 5, first_eq_second := _ }hello #eval ((dbg_trace "hello"; 5 : Nat) : Twice Nat)
hello
{ first := 5, second := 5, first_eq_second := _ }

Inlining the helper into the Coe instance results in a term that duplicates the Lean.Parser.Term.dbgTrace : term`dbg_trace e; body` evaluates to `body` and prints `e` (which can be an interpolated string literal) to stderr. It should only be used for debugging. dbg_trace:

instance : Coe α (Twice α) where coe x := x, x, rfl { first := 5, second := 5, first_eq_second := _ }hello hello #eval ((dbg_trace "hello"; 5 : Nat) : Twice Nat)
hello
hello
{ first := 5, second := 5, first_eq_second := _ }

Introducing an intermediate name for the result of the evaluation prevents the duplicated work:

instance : Coe α (Twice α) where coe x := let y := x; y, y, rfl { first := 5, second := 5, first_eq_second := _ }hello #eval ((dbg_trace "hello"; 5 : Nat) : Twice Nat)
hello
{ first := 5, second := 5, first_eq_second := _ }

5.6.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

5.6.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.

syntaxCoercion 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

5.6.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

Type class for the canonical homomorphism Nat R.

Instance Constructor

NatCast.mk.{u}

Methods

natCast : NatR

The canonical map Nat R.

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

Canonical homomorphism from Nat to a type R.

It contains just the function, with no axioms. In practice, the target type will likely have a (semi)ring structure, and this homomorphism should be a ring homomorphism.

The prototypical example is Int.ofNat.

This class 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.

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

The canonical homomorphism Int R. In most use cases R will have a ring structure and this will be a ring homomorphism.

Instance Constructor

IntCast.mk.{u}

Methods

intCast : IntR

The canonical map Int R.

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

Apply the canonical homomorphism from Int to a type R from an IntCast R instance.

In Mathlib there will be such a homomorphism whenever R is an additive group with a 1.

5.6.3. Coercing to Sorts🔗

The Lean elaborator expects types in certain positions without necessarily being able to determine the type's universe ahead of time. For example, the term following the colon in a definition header might be a proposition or a type. The ordinary coercion mechanism is not applicable because it requires a specific expected type, and there's no way to express that the expected type could be any universe in the Coe class.

When a term is elaborated in a position where a proposition or type is expected, but the inferred type of the elaborated term is not a proposition or type, Lean attempts to recover from the error by synthesizing an instance of CoeSort. If the instance is found, and the resulting type is itself a type, then it the coercion is inserted and unfolded.

Not every situation in which the elaborator expects a universe requires CoeSort. In some cases, a particular universe is available as an expected type. In these situations, ordinary coercion insertion using CoeT is used. Instances of CoeSort can be used to synthesize instances of CoeOut, so no separate instance is needed to support this use case. In general, coercions to types should be implemented as CoeSort.

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

CoeSort α β is a coercion to a sort. β must be a universe, and this is triggered when a : α appears in a place where a type is expected, like (x : a) or a a. CoeSort instances apply to CoeOut as well.

Instance Constructor

CoeSort.mk.{u, v}

Methods

coe : αβ

Coerces a value of type α to β, which must be a universe.

syntaxExplicit Coercion to Sorts
term ::= ...
    | `↥ t` coerces `t` to a type.  term

Coercions to sorts can be explicitly triggered using the prefix operator.

Sort Coercions

A monoid is a type equipped with an associative binary operation and an identity element. While monoid structure can be defined as a type class, it can also be defined as a structure that “bundles up” the structure with the type:

structure Monoid where Carrier : Type u op : Carrier Carrier Carrier id : Carrier op_assoc : (x y z : Carrier), op x (op y z) = op (op x y) z id_op_identity : (x : Carrier), op id x = x op_id_identity : (x : Carrier), op x id = x

The type Monoid does not indicate the carrier:

def StringMonoid : Monoid where Carrier := String op := (· ++ ·) id := "" op_assoc := ∀ (x y z : String), (fun x1 x2 => x1 ++ x2) x ((fun x1 x2 => x1 ++ x2) y z) = (fun x1 x2 => x1 ++ x2) ((fun x1 x2 => x1 ++ x2) x y) z x✝:Stringy✝:Stringz✝:String(fun x1 x2 => x1 ++ x2) x✝ ((fun x1 x2 => x1 ++ x2) y✝ z✝) = (fun x1 x2 => x1 ++ x2) ((fun x1 x2 => x1 ++ x2) x✝ y✝) z✝; All goals completed! 🐙 id_op_identity := ∀ (x : String), (fun x1 x2 => x1 ++ x2) "" x = x x✝:String(fun x1 x2 => x1 ++ x2) "" x✝ = x✝; All goals completed! 🐙 op_id_identity := ∀ (x : String), (fun x1 x2 => x1 ++ x2) x "" = x x✝:String(fun x1 x2 => x1 ++ x2) x✝ "" = x✝; All goals completed! 🐙

However, a CoeSort instance can be implemented that applies the Monoid.Carrier projection when a monoid is used in a position where Lean would expect a type:

instance : CoeSort Monoid (Type u) where coe m := m.Carrier example : StringMonoid := "hello"
Sort Coercions as Ordinary Coercions

The inductive type NatOrBool represents the types Nat and Bool. The can be coerced to the actual types Nat and Bool:

inductive NatOrBool where | nat | bool @[coe] abbrev NatOrBool.asType : NatOrBool Type | .nat => Nat | .bool => Bool instance : CoeSort NatOrBool Type where coe := NatOrBool.asType open NatOrBool

The CoeSort instance is used when nat occurs to the right of a colon:

def x : nat := 5

When an expected type is available, ordinary coercion insertion is used. In this case, the CoeSort instance is used to synthesize a CoeOut NatOrBool Type instance, which chains with the Coe Type (Option Type) instance to recover from the type error.

def y : Option Type := bool

5.6.4. Coercing to Function Types🔗

Another situation where an expected type is not generally available is the function position in a function application term. Dependent function types are common; together with implicit parameters, they cause information to flow from the elaboration of one argument to the elaboration of the others. Attempting to deduce the type required for the function from the expected type of the entire application term and individually-inferred types of arguments will often fail. In these situations, Lean uses the CoeFun type class to coerce a non-function in an application position into a function. Like CoeSort, CoeFun instances do not chain with other coercions while inserting a function coercion, but they can be used as CoeOut instances during ordinary coercion insertion.

The second parameter to CoeFun is an output parameter that determines the resulting function type. This output parameter is function that computes the function type from the term that's being coerced, rather than the function type itself. Unlike CoeDep, the term itself is not taken into account during instance synthesis; it can, however, be used to create dependently typed coercions where the function type is determined by the term.

🔗type class
CoeFun.{u, v} (α : Sort u)
  (γ : outParam (αSort v)) :
  Sort (max (max 1 u) v)

CoeFun α (γ : α Sort v) is a coercion to a function. γ a should be a (coercion-to-)function type, and this is triggered whenever an element f : α appears in an application like f x, which would not make sense since f does not have a function type. CoeFun instances apply to CoeOut as well.

Instance Constructor

CoeFun.mk.{u, v}

Methods

coe : (f : α) → γ f

Coerces a value f : α to type γ f, which should be either be a function type or another CoeFun type, in order to resolve a mistyped application f x.

syntaxExplicit Coercion to Functions
term ::= ...
    | `⇑ t` coerces `t` to a function.  term
Coercing Decorated Functions to Function Types

The structure NamedFun α β pairs a function from α to β with a name.

structure NamedFun (α : Type u) (β : Type v) where function : α β name : String

Existing functions can be named:

def succ : NamedFun Nat Nat where function n := n + 1 name := "succ" def asString [ToString α] : NamedFun α String where function := ToString.toString name := "asString" def append : NamedFun (List α) (List α List α) where function := (· ++ ·) name := "append"

Named functions can also be composed:

def NamedFun.comp (f : NamedFun β γ) (g : NamedFun α β) : NamedFun α γ where function := f.function g.function name := f.name ++ " ∘ " ++ g.name

Unlike ordinary functions, named functions have a reasonable representation as a string:

instance : ToString (NamedFun α α'') where toString f := s!"#<{f.name}>" #<asString ∘ succ>#eval asString.comp succ
#<asString ∘ succ>

A CoeFun instance allows them to be applied just like ordinary functions:

instance : CoeFun (NamedFun α α'') (fun _ => α α'') where coe | f, _ => f [1, 2, 3, 4, 5, 6]#eval append [1, 2, 3] [4, 5, 6]
[1, 2, 3, 4, 5, 6]
Dependent Coercion to Functions

Sometimes, the type of the resulting function depends on the specific value that is being coerced. A Writer represents a means of appending a representation of some value to a string:

structure Writer where Writes : Type u write : Writes String String def natWriter : Writer where Writes := Nat write n out := out ++ toString n def stringWriter : Writer where Writes := String write s out := out ++ s

Because the type of the parameter expected by the inner function depend on the Writer.Writes field, the CoeFun instance extracts the field:

instance : CoeFun Writer (·.Writes String String) where coe w := w.write

With this instance, concrete Writers can be used as functions:

"5 hello"#eval "" |> natWriter (5 : Nat) |> stringWriter " hello"
"5 hello"
Coercing to Function Types

A well-typed interpreter is an interpreter for a programming language that uses indexed families to rule out run-time type errors. Functions written in the interpreted language can be interpreted as Lean functions, but their underlying source code can also be inspected.

The first step in the well-typed interpreter is to select the subset of Lean types that can be used. These types are represented by an inductive type of codes Ty and a function that maps these codes to actual types.

inductive Ty where | nat | arr (dom cod : Ty) abbrev Ty.interp : Ty Type | .nat => Nat | .arr t t' => t.interp t'.interp

The language itself is represented by an indexed family over variable contexts and result types. Variables are represented by de Bruijn indices.

inductive Tm : List Ty Ty Type where | zero : Tm Γ .nat | succ (n : Tm Γ .nat) : Tm Γ .nat | rep (n : Tm Γ .nat) (start : Tm Γ t) (f : Tm Γ (.arr .nat (.arr t t))) : Tm Γ t | lam (body : Tm (t :: Γ) t') : Tm Γ (.arr t t') | app (f : Tm Γ (.arr t t')) (arg : Tm Γ t) : Tm Γ t' | var (i : Fin Γ.length) : Tm Γ Γ[i] deriving Repr

Because the OfNat instance for Fin requires that the upper bound be non-zero, Tm.var can be inconvenient to use with numeric literals. The helper Tm.v can be used to avoid the need for type annotations in these cases.

def Tm.v (i : Fin (Γ.length + 1)) : Tm (t :: Γ) (t :: Γ)[i] := .var (Γ := t :: Γ) i

A function that adds two natural numbers uses the rep operation to apply the successor Tm.succ repeatedly.

def plus : Tm [] (.arr .nat (.arr .nat .nat)) := .lam <| .lam <| .rep (.v 1) (.v 0) (.lam (.lam (.succ (.v 0))))

Each typing context can be interpreted as a type of run-time environments that provide a value for each variable in the context:

def Env : List Ty Type | [] => Unit | t :: Γ => t.interp × Env Γ def Env.empty : Env [] := () def Env.extend (ρ : Env Γ) (v : t.interp) : Env (t :: Γ) := (v, ρ) def Env.get (i : Fin Γ.length) (ρ : Env Γ) : Γ[i].interp := match Γ, ρ, i with | _::_, (v, _), 0, _ => v | _::_, (_, ρ'), i+1, _ => ρ'.get i, Γ:List Tyi✝:Fin Γ.lengthρ:Env Γhead✝:Tytail✝:List Tyfst✝:head✝.interpρ':Env tail✝i:NatisLt✝:i + 1 < (head✝ :: tail✝).lengthi < tail✝.length All goals completed! 🐙

Finally, the interpreter is a recursive function over the term:

def Tm.interp (ρ : Env α'') : Tm α'' t t.interp | .zero => 0 | .succ n => n.interp ρ + 1 | .rep n start f => let f' := f.interp ρ (n.interp ρ).fold (fun n _ x => f' n x) (start.interp ρ) | .lam body => fun x => body.interp (ρ.extend x) | .app f arg => f.interp ρ (arg.interp ρ) | .var i => ρ.get i

Coercing a Tm to a function consists of calling the interpreter.

instance : CoeFun (Tm [] α'') (fun _ => α''.interp) where coe f := f.interp .empty

Because functions are represented by a first-order inductive type, their code can be inspected:

Tm.lam (Tm.lam (Tm.rep (Tm.var 1) (Tm.var 0) (Tm.lam (Tm.lam (Tm.succ (Tm.var 0))))))#eval plus
Tm.lam (Tm.lam (Tm.rep (Tm.var 1) (Tm.var 0) (Tm.lam (Tm.lam (Tm.succ (Tm.var 0))))))

At the same time, due to the coercion, they can be applied just like native Lean functions:

8#eval plus 3 5
8

5.6.5. Implementation Details🔗

Only ordinary coercion insertion uses chaining. Inserting coercions to a sort or a function uses ordinary instance synthesis. Similarly, dependent coercions are not chained.

5.6.5.1. Unfolding Coercions🔗

The coercion insertion mechanism unfolds applications of coercions, which allows them to control the specific shape of the resulting term. This is important both to ensure readable proof goals and to control evaluation of the coerced term in compiled code. Unfolding coercions is controlled by the coe_decl attribute, which is applied to each coercion method (e.g. Coe.coe). This attribute should be considered part of the internals of the coercion mechanism, rather than part of the public coercion API.

5.6.5.2. Coercion Chaining🔗

Coercion chaining is implemented through a collection of auxiliary type classes. Users should not write instances of these classes directly, but knowledge of their structure can be useful when diagnosing the reason why a coercion was not inserted as expected. The specific rules governing the ordering of instances in the chain (namely, that it should match CoeHead?CoeOut*Coe*CoeTail?) are implemented by the following type classes:

  • CoeTC is the transitive closure of Coe instances.

  • CoeOTC is the middle of the chain, consisting of the transitive closure of CoeOut instances followed by CoeTC.

  • CoeHTC is the start of the chain, consisting of at most one CoeHead instance followed by CoeOTC.

  • CoeHTCT is the whole chain, consisting of CoeHTC followed by at most one CoeTail instance. Alternatively, it might be a NatCast instance.

  • CoeT represents the entire chain: it is either a CoeHTCT chain or a single CoeDep instance.

A graphical representation of the relationships between the coercion transitive closure auxiliary classes

Auxiliary Classes for Coercions
🔗type class
CoeHTCT.{u, v} (α : Sort u) (β : Sort v) :
  Sort (max (max 1 u) v)

Auxiliary class implementing CoeHead* Coe* CoeTail?. Users should generally not implement this directly.

Instance Constructor

CoeHTCT.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
CoeHTC.{u, v} (α : Sort u) (β : Sort v) :
  Sort (max (max 1 u) v)

Auxiliary class implementing CoeHead CoeOut* Coe*. Users should generally not implement this directly.

Instance Constructor

CoeHTC.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
CoeOTC.{u, v} (α : Sort u) (β : Sort v) :
  Sort (max (max 1 u) v)

Auxiliary class implementing CoeOut* Coe*. Users should generally not implement this directly.

Instance Constructor

CoeOTC.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
CoeTC.{u, v} (α : Sort u) (β : Sort v) :
  Sort (max (max 1 u) v)

Auxiliary class implementing Coe*. Users should generally not implement this directly.

Instance Constructor

CoeTC.mk.{u, v}

Methods

coe : αβ

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