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.
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: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:
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.
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
NatCastR instance) whenever R is an additive monoid with a 1.
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
NatCastR instance) whenever R is an additive monoid with a 1.
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
IntCastR instance) whenever R is an additive group with a 1.
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
IntCastR instance) whenever R is an additive group with a 1.