The Lean Language Reference

12.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 := _ }