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:
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 TSyntaxc1 to TSyntaxc2 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.
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 }#printth
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 OfNatNat, 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.bdiv2
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.
Even if Bin.ofNat is registered as a coercion, natural number literals cannot be used for Bin:
attribute[coe]Bin.ofNatinstance:CoeNatBinwherecoe:=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 OfNatBin instance:
Most new coercions can be defined by declaring an instance of the Coetype 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.
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.
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.
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.
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") "" }#printtomorrow
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:
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.
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):TwiceNat)
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α)wherecoex:=⟨x,x,rfl⟩{ first := 5, second := 5, first_eq_second := _ }hello
hello
#eval((dbg_trace"hello";5:Nat):TwiceNat)
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α)wherecoex:=lety:=x;⟨y,y,rfl⟩{ first := 5, second := 5, first_eq_second := _ }hello
#eval((dbg_trace"hello";5:Nat):TwiceNat)
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:
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.
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.
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.
Due to coercion chaining, there is also a coercion from Even to Int formed by chaining the CoeEvenNat instance with the existing coercion from Nat to Int:
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:
Ordinary type errors are produced for other strings:
#evaltype 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:
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.
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:
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.
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.
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.
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.
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.
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:
Arbitrary lists cannot, however, be coerced to non-empty lists, because some arbitrarily-chosen lists may indeed be empty:
instance:Coe(Listα)(NonEmptyListα)wherecoexs:=⟨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:
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#checkfun(xs:ListNat)=>letys:ListNat:=xs++[4]type mismatch
ys
has type
List Nat : Type
but is expected to have type
NonEmptyList Nat : Type(ys:NonEmptyListNat)
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 SubInt 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.
defsub(nk:Nat):Int:=n-kdef sub : Nat → Nat → Int :=
fun n k => ↑n - ↑k#printsub
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 SubNat instance is selected, leading to the difference being a Nat.
The difference is then coerced to an Int.
defsub'(nk:Nat):Int:=↑(n-k)def sub' : Nat → Nat → Int :=
fun n k => ↑(n - k)#printsub'
These two functions are not equivalent because subtraction of natural numbers truncates at zero:
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:CoeAB:=⟨myFn⟩ declaration) allows the delaborator to show
applications of this function as ↑ when printing expressions.
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:
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.
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.
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 NatCastR instance) whenever R is an additive monoid with a 1.
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.
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.
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:
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:
When an expected type is available, ordinary coercion insertion is used.
In this case, the CoeSort instance is used to synthesize a CoeOutNatOrBoolType instance, which chains with the CoeType(OptionType) instance to recover from the type error.
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.
CoeFunα(γ:α→Sortv) 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 fx, which would not make sense since
f does not have a function type.
CoeFun instances apply to CoeOut as well.
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:
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.
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.
Only ordinary coercion insertion uses chaining.
Inserting coercions to a sort or a function uses ordinary instance synthesis.
Similarly, dependent coercions are not chained.
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.
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: