<!DOCTYPE article PUBLIC "-//NLM//DTD JATS (Z39.96) Journal Archiving and Interchange DTD v1.0 20120330//EN" "JATS-archivearticle1.dtd">
<article xmlns:xlink="http://www.w3.org/1999/xlink">
  <front>
    <journal-meta />
    <article-meta>
      <title-group>
        <article-title>Simplifying Casts and Coercions (Extended Abstract)</article-title>
      </title-group>
      <contrib-group>
        <contrib contrib-type="author">
          <string-name>Robert Y. Lewis</string-name>
          <xref ref-type="aff" rid="aff1">1</xref>
        </contrib>
        <contrib contrib-type="author">
          <string-name>Paul-Nicolas Madelaine</string-name>
          <xref ref-type="aff" rid="aff0">0</xref>
        </contrib>
        <aff id="aff0">
          <label>0</label>
          <institution>École Normale Supérieure</institution>
          ,
          <addr-line>Paris</addr-line>
          ,
          <country country="FR">France</country>
        </aff>
        <aff id="aff1">
          <label>1</label>
          <institution>Vrije Universiteit Amsterdam</institution>
          ,
          <country country="NL">The Netherlands</country>
        </aff>
      </contrib-group>
      <fpage>53</fpage>
      <lpage>62</lpage>
      <abstract>
        <p>This paper introduces norm_cast, a toolbox of tactics for the Lean proof assistant designed to manipulate expressions containing coercions and casts. These expressions can be frustrating for beginning and expert users alike; the presence of coercions can cause seemingly identical expressions to fail to unify and rewrites to fail. The norm_cast tactics aim to make reasoning with such expressions as transparent as possible. They are used extensively to eliminate boilerplate arguments in the Lean mathematical library and in external developments.</p>
      </abstract>
      <kwd-group>
        <kwd>eol&gt;cast</kwd>
        <kwd>coercion</kwd>
        <kwd>Lean</kwd>
        <kwd>formal proof</kwd>
      </kwd-group>
    </article-meta>
  </front>
  <body>
    <sec id="sec-1">
      <title>1. Introduction</title>
      <p>theorem of_int {p : N} (hp : prime p) (z : Z) : padic_norm p ↑z ≤
1
where padic_norm : N →</p>
      <p>Q → Q. Straightforward steps reduce the proof to three goals:
To solve these goals by hand, the user must combine knowledge of library lemmas with lemmas
that manipulate casts. The latter obscure the main ideas of the proof:
{ rw [← nat.cast_one, nat.cast_le], exact le_of_lt hp.one_lt },
{ rw [padic_val_rat_of_int _ hp.ne_one hz, neg_nonpos],</p>
      <p>exact int.coe_nat_nonneg _ },
{ exact int.cast_ne_zero.2 hz }</p>
      <p>
        We introduce a family of tactics implemented in the Lean proof assistant [
        <xref ref-type="bibr" rid="ref2">2</xref>
        ] that aim to
remove these frustrations. The core tool, norm_cast, tries to rewrite an expression containing
casts to a normal form determined by a configurable collection of rewrite rules. Variants allow
the user to apply lemmas and hypotheses and rewrite the goal “modulo” the presence of casts.
The tool was developed to address usability issues raised while formalizing mathematical results
in Lean1 [
        <xref ref-type="bibr" rid="ref1 ref3">3, 1</xref>
        ]. It is incorporated into Lean’s mathematical library mathlib [
        <xref ref-type="bibr" rid="ref4">4</xref>
        ], where it is
already invoked 221 times, and is also used heavily in external libraries [
        <xref ref-type="bibr" rid="ref5">5</xref>
        ].
      </p>
      <p>Using our tool, the script above focuses on the relevant library lemmas:
{ exact_mod_cast le_of_lt hp.one_lt },
{ rw [padic_val_rat_of_int _ hp.ne_one hz, neg_nonpos],</p>
      <p>norm_cast; simp },
{ exact_mod_cast hz }</p>
      <p>Our tool is extensible: adapting it to new theories with new coercions simply requires tagging
certain library lemmas. It is largely independent of the underlying logic: for example, there
are no roadblocks to implementing it in systems without convertibility. It is not restricted to
concrete types like N, Z, and Q: it supports casts into abstract algebraic structures, casts out of
arbitrary subtypes, and in general any cast for which the user can prove at least one lemma of
one of the forms described in Section 4.</p>
      <p>We provide a website2 which points to our code in the mathlib repository, along with examples
of norm_cast in use. Our aim in this report is not to give a theoretical account of type inclusions,
but rather to present a powerful and lightweight procedure that is efective at dealing with
these situations in practice.</p>
      <p>“Coercion” and “cast” are sometimes used interchangeably in the literature, and “cast” can
also refer to the transport of a term t : A to the type B along a type equality A = B. In this
description, we take a cast ↑ : A → B to be simply a function; it typically preserves structure,
and is often injective, but neither is required. Casting a term t : A to B refers to applying the
(often canonical) cast. A coercion is a cast that is automatically inserted by the elaborator. We
do not consider casts along type equalities.</p>
      <p>1Lean users previously wrote a guide to managing casts by hand. This guide is archived with our supplementary
materials.</p>
      <p>2https://lean-forward.github.io/norm_cast</p>
    </sec>
    <sec id="sec-2">
      <title>2. Lean Specifics</title>
      <p>While the approach we describe can be adapted to other proof assistants, some details of this
report are specific to Lean. Here we describe some of the relevant features of Lean.</p>
      <p>
        Lean’s elaborator inserts coercions using type classes [
        <xref ref-type="bibr" rid="ref6 ref7">6, 7</xref>
        ]. Its generic coercion function has
signature
      </p>
      <p>coe : Π {a : Sort u} {b : Sort v} [has_lift_t a b], a → b
where the arguments a and b are implicit and the anonymous has_lift_t argument is inferred
by type class resolution. An instance of the type class has_lift_t a b witnesses a transitive
chain of coercions from a to b, avoiding loops caused by reflexive instances. When a function
application fails to typecheck, the elaborator will insert applications of coe and try to resolve the
resulting has_lift_t goal. Coercions are typically inserted at the leaf nodes of an expression.
Users can also manually insert casts by using coe directly, with prefix notation ↑.</p>
      <p>Type-polymorphic operators and relations like + and &lt; are also implemented with type classes.
Numerals build on top of these. A numeral is represented in binary by nested applications of
the following terms:
zero : Π (α : Type u) [has_zero α ], α
one : Π (α : Type u) [has_one α ], α
bit0 : Π {α : Type u} [has_add α ], α → α
bit1 : Π {α : Type u} [has_one α ] [has_add α ], α
→ α
Any type α that instantiates the classes has_zero α , has_one α , and has_add α supports
numeral notation, e.g. (5 : α ). While in this description we explicitly write the types of all
numerals, in practice they are typically inferred.</p>
      <p>
        Lean’s powerful metaprogramming framework [
        <xref ref-type="bibr" rid="ref8">8</xref>
        ] allows us to implement our tool in the
language of Lean itself and include it in mathlib. The framework provides an interface to
access the system’s routines for unification, type class resolution, simplification, and more.
Metaprograms can query and add to a Lean environment. Declarations in an environment can
be tagged with parametrized attributes, and metaprograms are able to define new attributes,
use them to tag declarations, and retrieve lists of tagged declarations.
      </p>
    </sec>
    <sec id="sec-3">
      <title>3. Outline of the Simplification Procedure</title>
      <p>The core routine in our procedure takes as input an expression and transforms the expression
to one in which applications of the cast function ↑ are simplified. It returns a proof that the
resulting expression is equal to the input. In the most common case, where the expression is a
proposition, the proof of equality serves as a proof of logical equivalence.</p>
      <p>As an example of the expected behavior, we simplify the expression ↑m + ↑n &lt; (10 : Z),
where m, n : N are cast to Z. The expected form of the output is m + n &lt; (10 : N), in
which no casts appear; recall that +, &lt;, and 10 are polymorphic. Our tool should proceed as
follows:
1. Replace the numeral on the right with the cast of a nat: ↑m + ↑n &lt; ↑(10 : N)
2. Factor ↑ to the outside of the left: ↑(m + n) &lt; ↑(10 : N)
3. Eliminate both casts to get an inequality over N: m + n &lt; (10 : N)</p>
      <p>Steps 2 and 3 are efectively just applications of Lean’s simplification API with certain rewrite
lemmas. Step 1 has a slightly diferent flavor, but we will still be able to use the simplification
API to implement this. Since the simplifier will handle cases of these patterns nested inside
larger expressions, we can focus on the atomic situation.</p>
      <p>Each of these steps must be justified by lemmas in the library, of course. They would not be
sound for arbitrary types, operations, and relations. Users of our tool tag certain declarations
with the attribute norm_cast, for example:
@[norm_cast]
theorem nat.cast_add {α : Type} [add_monoid α ] [has_one α ] (m n : N) :
(↑(m + n) : α ) = ↑m + ↑n := . . .</p>
      <p>Our tool sorts these tagged declarations into three categories.</p>
      <p>• move lemmas equate expressions with casts at the root to expressions with casts further
toward the leaves, e.g. ↑(m + n) = ↑m + ↑n. By mathlib convention, such lemmas are
stated with the root cast on the left of the equation; Step 2 uses them as right-to-left
rewrite rules.
• elim lemmas relate expressions with casts to expressions without, e.g. ↑a &lt; ↑b ↔ a
&lt; b. Such lemmas are stated with the expression containing casts on the left of the
relation; Step 3 uses them as left-to-right rewrite rules. These lemmas are not restricted
to propositional equivalences: they can also be used to modify polymorphic operations,
e.g. ‖↑a‖ = ‖a‖ for a real valued norm function defined on all normed spaces.
• squash lemmas equate expressions with one or more casts at the root to expressions
with fewer casts at the root, e.g. ↑(1 : N) = (1 : Z) and ↑↑n = ↑n. Such lemmas are
stated with the expression containing the larger number of casts on the left; Step 1 uses
them alongside move lemmas to justify that (10 : Z) = ↑(10 : N), and they are used
in the heuristic splitting step described below.</p>
      <p>To simplify expressions where casts come from a variety of sources, we must sometimes split
casts into pieces. Suppose n : N and z : Z, and consider the goal ↑n + ↑z = (2 : Q). (We
call the pattern P (↑x) (↑y), where P is a binary function or relation taking two arguments
of the same type and x and y are of diferent types, the heuristic splitting pattern.) We cannot
rewrite the left hand side to ↑(n + z), since the addition would not be well typed. However,
move and squash lemmas justify a transformation to ↑↑n + ↑z = ↑↑(2 : N), where the inner
casts go N → Z and the outer Z → Q. We transform this to ↑(↑n + z) = ↑↑(2 : N) and
then ↑n + z = ↑(2 : N). Finally, squash lemmas reduce the right hand side to the native
numeral (2 : Z).</p>
    </sec>
    <sec id="sec-4">
      <title>4. Implementation</title>
      <p>The core simplification routine has type expr → tactic (expr × expr), taking in an
expression and returning it in simplified form with a proof that the output is equal to the input.
Lean’s simplifier API provides methods for traversing and rewriting an expression from the
leaf nodes outward (“bottom up”) and in reverse (“top down”). Our routine consists of four
successive simplifier passes.</p>
      <p>1. Working top down, replace each numeral (num : α ) with ↑(num : N). Justify these
replacements with move lemmas to move casts down through applications of the constants
bit0 and bit1, and squash lemmas to change ↑(0 : N) and ↑(1 : N) to (0 : α ) and
(1 : α ).
2. Working bottom up, move casts upward by rewriting with move lemmas and eliminate
them when possible by rewriting with elim lemmas. If no rewrite rules apply to a
subexpression that matches the heuristic splitting pattern, fire the splitting procedure
described below.
3. Working top down, clean up any unused repeated casts that were inserted by the heuristic
by rewriting with squash lemmas.
4. Working top down, restore numerals to their natively typed form as in Step 1. This is
again justified by move and squash lemmas.</p>
      <p>The splitting procedure fires on an expression of the form P (↑x) (↑y), where P is a binary
function or relation, x : X and y : Y are both cast to type Z, and X and Y are not equal. The
procedure tries to find a coercion from X to Y or vice versa. The existence of a coercion is
expressed as a type class instance, so this can be tested by trying to resolve a type class goal
has_lift_t X Y. Supposing the former coercion is found, the procedure tries to replace ↑x
with ↑↑x, where the nested coercions go from X to Y to Z. This is justified using squash lemmas.</p>
      <p>We use Lean’s user attribute API to define an attribute norm_cast. This attribute is applied
by the user to a lemma at or after the time of declaration. It tags the lemma for use in the
procedure. A norm_cast lemma has the form lhs = rhs or lhs ↔ rhs, typically preceded
by a sequence of quantifiers. In nearly all cases, the attribute handler can automatically classify
a lemma as elim, move, or squash.</p>
      <p>Head casts are applications of casts that appear at the root of the expression tree, as in ↑↑
(x+y), and internal casts appear elsewhere. Let ℋ(e) and ℐ(e) denote the number of head casts
and internal casts in e. Based on the number and positions of applications of casts, we classify
a lemma as
elim if ℋ(lhs) = 0 and ℐ(lhs) ≥ 1
move if ℋ(lhs) = 1, ℐ(lhs) = ℋ(rhs) = 0, and ℐ(rhs) ≥ 1.</p>
      <p>squash if ℋ(lhs) ≥ 1, ℐ(lhs) = ℐ(rhs) = 0, and ℋ(lhs) &gt; ℋ(rhs).</p>
      <p>When a lemma does not fit in any of these categories is tagged with the norm_cast attribute,
an error is produced.</p>
      <p>This classification applies to both = and ↔ lemmas. While users can override the classification
by providing a parameter to the attribute, this is done for only 4 out of 424 attributions in
mathlib. Lean’s user attribute API allows us to cache the set of classified lemmas in a format
convenient for the simplifier, so the classifier creates very little overhead.</p>
      <p>
        A previous version of norm_cast relied on users classifying their lemmas manually. However,
misclassified lemmas can lead to errant behavior that is hard to diagnose. From the perspective
of library maintenance [
        <xref ref-type="bibr" rid="ref9">9</xref>
        ], it is much cleaner to automatically classify the rewrite rules, and we
slightly redesigned the procedure to make this possible.
      </p>
    </sec>
    <sec id="sec-5">
      <title>5. Interface</title>
      <p>We provide a suite of tactics built around the core norm_cast functionality. These try to
replicate the behavior of other Lean tactics “modulo casts,” so that users can use familiar idioms
while ignoring the presence of casts.</p>
      <p>The core tactic norm_cast simplifies the current goal. Alternatively, norm_cast at h
simplifies the type of a hypothesis h. These versions of the tactic are useful for cleaning up the
proof state; while they rarely close a proof obligation, they make the goal easier to work on and
hypotheses easier to use.</p>
      <p>A variant exact_mod_cast t simplifies both the goal and the type of the expression t, and
tries to use t to close the goal; apply_mod_cast t does similar, but allows arguments to t
to generate new subgoals. To close the goal with a hypothesis, assumption_mod_cast will
try exact_mod_cast on all plausible candidates, in the local context. Finally, rw_mod_cast
[l1, . . ., l] will use a list of lemmas to sequentially rewrite the goal, calling norm_cast in
between rewrite steps. This generalizes the behavior of Lean’s standard rewrite tactic rw. These
variants all serve a common purpose, namely, to generalize Lean’s core tactic language so that
it works as expected in the presence of casts. The exact, apply, assumption, and rw tactics
are fundamental, and the mod_cast versions serve to extend the situations in which they can
be used.</p>
      <p>We also add move and squash lemmas into a custom simp lemma collection and define a
tactic push_cast that simplifies with this collection; note that push_cast does not directly use
the norm_cast method. Calling push_cast simplifies in the opposite direction to norm_cast,
meaning that casts get pushed toward the leaf nodes of expressions. This does not allow casts
to be eliminated over relations, but can be useful in its own right.</p>
    </sec>
    <sec id="sec-6">
      <title>6. Examples</title>
      <p>The norm_cast test file 3 in mathlib demonstrates the tool in action. As a first example, we
walk through a test where the heuristic splitting procedure is needed:
n : N, z : Z, h : ↑n - ↑z &lt; (5 : Q)
⊢
↑n - z &lt; (5 : Z)
Using exact_mod_cast h will simplify h to match the goal, which is already in normal form.
After changing (5 : Q) to ↑(5 : N), norm_cast will fail to fire any move or elim rewrites.
It will notice that ↑n - ↑z matches the heuristic splitting pattern, and rewrite ↑n to ↑↑n, where
the inner cast goes N → Z and the outer goes Z → Q. A move rule will then match, and
the expression will be rewritten to ↑(↑n - z) &lt; ↑(5 : N). While both sides of the &lt; are
now casts to Q, the left comes from Z and the right from N, so no elim rule will fire; instead,
3https://github.com/leanprover-community/mathlib/blob/master/test/norm_cast.lean
norm_cast will match the entire expression to the heuristic splitting pattern and rewrite the
right side to ↑↑(5 : N). It can then rewrite with an elim lemma ↑a &lt; ↑b ↔ a &lt; b to obtain
↑n - z &lt; ↑(5 : N), and finally normalize the numeral on the right to (5 : Z).</p>
      <p>There is nothing special about the embedding domain Q in the above example. The theorem
holds when n and z are embedded into any linear ordered ring.</p>
      <p>example (α : Type) [linear_ordered_ring α ] (n : N) (z : Z)</p>
      <p>(h : ↑n - ↑z &lt; (5 : α )) : ↑n - z &lt; (5 : Z) :=
by exact_mod_cast h</p>
      <p>Lean’s simplifier supports conditional rewriting, and norm_cast makes use of this support.
Note that the following example does not hold when n &gt; m, since m - n = 0.
example (m n : N) (h : n ≤ m) : ↑(m - n) = (↑m - ↑n : Z) :=
by norm_cast</p>
      <p>
        The norm_cast family of tactics is used throughout mathlib. It is particularly useful in the
development of the -adic numbers Q and integers Z [
        <xref ref-type="bibr" rid="ref1">1</xref>
        ]. The rationals Q are embedded in
the -adics, and the definition of Q requires working with a natural number  embedded in Z
and Q; furthermore, Z is a subtype of Q. This development makes 64 calls to tactics in the
norm_cast family.
      </p>
      <p>A lemma in the development of Q bounds the -adic norm of an integer:
lemma le_of_dvd {n : N} {z : Z} (hd : ↑(p^n) | z) :</p>
      <p>padic_norm p ↑z ≤ ↑ p ^ -(↑n : Z)
The mathlib proof of this lemma calls exact_mod_cast four times, to close subgoals:
• 0 ≤ p ⊢ 0 ≤ ↑ p
• 1 ≤ p ⊢ 1 ≤ ↑ p
• ↑(p ^ n) | z ⊢ ↑p ^ n | z
• ↑z ̸= 0 ⊢ z ̸= 0
Theprooforiginallywrittenwithoutnorm_castcontainsfiveexplicitreferencestocastlemmas,
and uses an explicit intermediate step that is unnecessary in the mathlib proof:
have hp’ : (↑p : Q) ≥ 1, from</p>
      <p>show ↑p ≥ ↑ (1 : N), from cast_le.2 (le_of_lt hp.gt_one)</p>
      <p>The tool is particularly useful alongside the lift tactic, which conditionally embeds terms
in other types. In the following library lemma about the extended nonnegative reals ennreal,
lifting two ennreals to the type of nonnegative reals is justified by hypotheses that they are
not infinite. In the resulting goal</p>
      <p>a b : nnreal ⊢ ennreal.to_real ↑a ≤ ennreal.to_real ↑b ↔ ↑a ≤ ↑ b
thecastsontheleftarennreal → ennreal; thegoalisdischargedimmediatelybynorm_cast.
lemma to_real_le_to_real {a b : ennreal} (ha : a ̸= ⊤) (hb : b ̸= ⊤) :
ennreal.to_real a ≤ ennreal.to_real b ↔ a ≤ b :=
by { lift a to nnreal using ha, lift b to nnreal using hb, norm_cast }</p>
      <p>
        Buzzard, Commelin, and Massot use norm_cast 53 times in their definition of a perfectoid
space [
        <xref ref-type="bibr" rid="ref5">5</xref>
        ]. A typical use case is to match hypotheses from the units subtype of a monoid to
goals stated in the monoid itself, e.g.:
γ γ 0 : units (Γ0 R), h : γ 0 * γ 0 ≤ γ
⊢
↑γ 0 * ↑γ 0 ≤ ↑ γ
These goals are rarely provable by conversion because of the complicated algebraic structure
on the types involved. While traditional formalizations often make design decisions to limit
the presence of coercions, they seem to be unavoidable in deep mathematical formalizations.
Buzzard, Commelin, and Massot write that norm_cast “greatly alleviates . . . pain” in their
project.
      </p>
      <p>The performance of norm_cast is not a major concern: it depends mainly on the speed
of Lean’s simplifier, which is heavily optimized and called regularly on complex goals. Since
norm_cast calls the simplifier with a restricted set of rewrite rules, it normally sees close to
best-case performance. It is very uncommon in practice to see norm_cast take more than a
fraction of a second to simplify an expression. The rare cases in which it does are cases where
other tactics, including full and definitional simplification, also tend to struggle. We do not know
of any benchmark suites for this type of problem and have not done extensive performance
testing.</p>
    </sec>
    <sec id="sec-7">
      <title>7. Conclusion</title>
      <p>The norm_cast family of tactics can be seen as a variant of simplification procedures, which
are common tools in proof assistants. Indeed, push_cast is a straightforward application of
Lean’s simplifier, and similar functionality is found in many other systems, often in the default
set of simplification lemmas.</p>
      <p>
        Isabelle’s standard simplifier [
        <xref ref-type="bibr" rid="ref10">10</xref>
        ] is more powerful than Lean’s, but to our knowledge, the
system has no tool like norm_cast. Some theories may set up simp lemmas in a style that
approximates our procedure, particularly for use with transfer [
        <xref ref-type="bibr" rid="ref11">11</xref>
        ]. But it appears that
approaches to managing and eliminating casts tend to be ad hoc combinations of simplification
and manual work.
      </p>
      <p>
        In Coq, unification hints [
        <xref ref-type="bibr" rid="ref12">12</xref>
        ] can sometimes help to unify terms that difer in the placement of
coercions. When the needed definitional equalities hold, the behavior of assumption_mod_cast
and exact_mod_cast can be replicated by computation. The same is true in Lean, and one
benefit of norm_cast is that it works even in the absence of definitional equalities, as is often the
case with abstract algebraic structures. It is dificult to replicate the behavior of rw_mod_cast
using computation without providing more explicit information. The ppsimpl preprocessing
tool [
        <xref ref-type="bibr" rid="ref13">13</xref>
        ], which tries to eliminate inconvenient types and constants from a goal, shares some
design features with norm_cast.
      </p>
      <p>The norm_cast family aims to eliminate a source of frustration found when formalizing
mathematical topics. The metaprogramming features of Lean allow it to be implemented
in a lightweight and extensible way. Its development was inspired by discussion between
mathematical formalizers and tactic writers. We hypothesize that there are many other similarly
lightweight tools that would help to move proof assistants closer to the mathematical vernacular.</p>
      <p>The tool is inherently coupled to its ambient library, meaning that it is only efective when
the proper lemmas are tagged for its use. We thus consider it a mistake to consider tactic
writing and library development separately. The norm_cast tool and its corresponding lemma
attributions are part of mathlib, and despite not being themselves definitions or proofs, they
constitute a diferent, procedural, kind of mathematical knowledge.</p>
      <p>Acknowledgments. We thank Jasmin Blanchette for helpful comments, Gabriel Ebner for
contributions to the code, and the mathlib community for extensive stress-testing. We thank
an anonymous reviewer for enlightening examples of how such problems can be dealt with in
other systems.</p>
      <p>We acknowledge support from the European Research Council (ERC) under the European
Union’s Horizon 2020 research and innovation program (grant agreement No. 713999,
Matryoshka) and from the Dutch Research Council (NWO) under the Vidi program (project No.
016.Vidi.189.037, Lean Forward).</p>
    </sec>
  </body>
  <back>
    <ref-list>
      <ref id="ref1">
        <mixed-citation>
          [1]
          <string-name>
            <given-names>R. Y.</given-names>
            <surname>Lewis</surname>
          </string-name>
          ,
          <article-title>A formal proof of Hensel's lemma over the -adic integers</article-title>
          ,
          <source>in: Proceedings of the 8th ACM SIGPLAN International Conference on Certified Programs and Proofs</source>
          ,
          <string-name>
            <surname>CPP</surname>
          </string-name>
          <year>2019</year>
          , Cascais, Portugal, January
          <volume>14</volume>
          -
          <issue>15</issue>
          ,
          <year>2019</year>
          ,
          <year>2019</year>
          , pp.
          <fpage>15</fpage>
          -
          <lpage>26</lpage>
          . URL: https://doi.org/10.1145/ 3293880.3294089. doi:
          <volume>10</volume>
          .1145/3293880.3294089.
        </mixed-citation>
      </ref>
      <ref id="ref2">
        <mixed-citation>
          [2] L. de Moura,
          <string-name>
            <given-names>S.</given-names>
            <surname>Kong</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Avigad</surname>
          </string-name>
          ,
          <string-name>
            <surname>F. van Doorn</surname>
          </string-name>
          ,
          <source>J. von Raumer</source>
          ,
          <article-title>The Lean Theorem Prover (system description), in:</article-title>
          <string-name>
            <given-names>A. P.</given-names>
            <surname>Felty</surname>
          </string-name>
          ,
          <string-name>
            <surname>A</surname>
          </string-name>
          . Middeldorp (Eds.),
          <source>CADE-25</source>
          , Springer International Publishing, Cham,
          <year>2015</year>
          , pp.
          <fpage>378</fpage>
          -
          <lpage>388</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref3">
        <mixed-citation>
          [3]
          <string-name>
            <given-names>S. R.</given-names>
            <surname>Dahmen</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Hölzl</surname>
          </string-name>
          ,
          <string-name>
            <given-names>R. Y.</given-names>
            <surname>Lewis</surname>
          </string-name>
          ,
          <article-title>Formalizing the solution to the cap set problem</article-title>
          , in: J.
          <string-name>
            <surname>Harrison</surname>
          </string-name>
          ,
          <string-name>
            <surname>J. O'Leary</surname>
            ,
            <given-names>A</given-names>
          </string-name>
          . Tolmach (Eds.),
          <source>10th International Conference on Interactive Theorem Proving (ITP</source>
          <year>2019</year>
          ), volume
          <volume>141</volume>
          of Leibniz International Proceedings in Informatics (LIPIcs),
          <source>Schloss Dagstuhl-Leibniz-Zentrum fuer Informatik</source>
          , Dagstuhl, Germany,
          <year>2019</year>
          , pp.
          <volume>15</volume>
          :
          <fpage>1</fpage>
          -
          <lpage>15</lpage>
          :
          <fpage>19</fpage>
          . doi:
          <volume>10</volume>
          .4230/LIPIcs.ITP.
          <year>2019</year>
          .
          <volume>15</volume>
          .
        </mixed-citation>
      </ref>
      <ref id="ref4">
        <mixed-citation>
          <article-title>[4] The mathlib Community, The Lean mathematical library</article-title>
          ,
          <source>in: Proceedings of the 9th ACM SIGPLAN International Conference on Certified Programs and Proofs</source>
          ,
          <string-name>
            <surname>CPP</surname>
          </string-name>
          <year>2020</year>
          ,
          <article-title>Association for Computing Machinery</article-title>
          , New York, NY, USA,
          <year>2020</year>
          , p.
          <fpage>367</fpage>
          -
          <lpage>381</lpage>
          . URL: https: //doi.org/10.1145/3372885.3373824. doi:
          <volume>10</volume>
          .1145/3372885.3373824.
        </mixed-citation>
      </ref>
      <ref id="ref5">
        <mixed-citation>
          [5]
          <string-name>
            <given-names>K.</given-names>
            <surname>Buzzard</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Commelin</surname>
          </string-name>
          ,
          <string-name>
            <given-names>P.</given-names>
            <surname>Massot</surname>
          </string-name>
          ,
          <article-title>Formalising perfectoid spaces</article-title>
          ,
          <source>in: Proceedings of the 9th ACM SIGPLAN International Conference on Certified Programs and Proofs</source>
          ,
          <string-name>
            <surname>CPP</surname>
          </string-name>
          <year>2020</year>
          ,
          <article-title>Association for Computing Machinery</article-title>
          , New York, NY, USA,
          <year>2020</year>
          , p.
          <fpage>299</fpage>
          -
          <lpage>312</lpage>
          . URL: https://doi.org/10.1145/3372885.3373830. doi:
          <volume>10</volume>
          .1145/3372885.3373830.
        </mixed-citation>
      </ref>
      <ref id="ref6">
        <mixed-citation>
          [6]
          <string-name>
            <given-names>D.</given-names>
            <surname>Selsam</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Ullrich</surname>
          </string-name>
          , L. de Moura, Tabled typeclass resolution,
          <year>2020</year>
          . arXiv:
          <year>2001</year>
          .04301.
        </mixed-citation>
      </ref>
      <ref id="ref7">
        <mixed-citation>
          [7]
          <string-name>
            <given-names>P.</given-names>
            <surname>Wadler</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Blott</surname>
          </string-name>
          ,
          <article-title>How to make ad-hoc polymorphism less ad-hoc</article-title>
          ,
          <source>in: Conference Record of the Sixteenth Annual ACM Symposium on Principles of Programming Languages</source>
          , Austin, Texas, USA, January
          <volume>11</volume>
          -
          <issue>13</issue>
          ,
          <year>1989</year>
          ,
          <year>1989</year>
          , pp.
          <fpage>60</fpage>
          -
          <lpage>76</lpage>
          . URL: https://doi.org/10.1145/75277.75283. doi:
          <volume>10</volume>
          .1145/75277.75283.
        </mixed-citation>
      </ref>
      <ref id="ref8">
        <mixed-citation>
          [8]
          <string-name>
            <given-names>G.</given-names>
            <surname>Ebner</surname>
          </string-name>
          ,
          <string-name>
            <given-names>S.</given-names>
            <surname>Ullrich</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Roesch</surname>
          </string-name>
          ,
          <string-name>
            <given-names>J.</given-names>
            <surname>Avigad</surname>
          </string-name>
          , L. de Moura,
          <article-title>A metaprogramming framework for formal verification</article-title>
          ,
          <source>PACMPL</source>
          <volume>1</volume>
          (
          <year>2017</year>
          )
          <volume>34</volume>
          :
          <fpage>1</fpage>
          -
          <lpage>34</lpage>
          :
          <fpage>29</fpage>
          . URL: https://doi.org/10.1145/3110278. doi:
          <volume>10</volume>
          .1145/3110278.
        </mixed-citation>
      </ref>
      <ref id="ref9">
        <mixed-citation>
          [9]
          <string-name>
            <surname>F. van Doorn</surname>
          </string-name>
          ,
          <string-name>
            <given-names>G.</given-names>
            <surname>Ebner</surname>
          </string-name>
          ,
          <string-name>
            <given-names>R. Y.</given-names>
            <surname>Lewis</surname>
          </string-name>
          ,
          <article-title>Maintaining a library of formal mathematics</article-title>
          , in: C.
          <string-name>
            <surname>Benzmüller</surname>
            ,
            <given-names>B.</given-names>
          </string-name>
          <string-name>
            <surname>Miller</surname>
          </string-name>
          (Eds.),
          <source>Intelligent Computer Mathematics</source>
          , Springer International Publishing, Cham,
          <year>2020</year>
          , pp.
          <fpage>251</fpage>
          -
          <lpage>267</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref10">
        <mixed-citation>
          [10]
          <string-name>
            <given-names>T.</given-names>
            <surname>Nipkow</surname>
          </string-name>
          ,
          <string-name>
            <given-names>L. C.</given-names>
            <surname>Paulson</surname>
          </string-name>
          , M. Wenzel, Isabelle/HOL:
          <article-title>a proof assistant for higher-order logic</article-title>
          , volume
          <volume>2283</volume>
          ,
          <string-name>
            <surname>Springer</surname>
            <given-names>Science</given-names>
          </string-name>
          &amp; Business
          <string-name>
            <surname>Media</surname>
          </string-name>
          ,
          <year>2002</year>
          .
        </mixed-citation>
      </ref>
      <ref id="ref11">
        <mixed-citation>
          [11]
          <string-name>
            <given-names>B.</given-names>
            <surname>Hufman</surname>
          </string-name>
          ,
          <string-name>
            <given-names>O.</given-names>
            <surname>Kunčar</surname>
          </string-name>
          ,
          <article-title>Lifting and Transfer: A modular design for quotients in Isabelle/HOL</article-title>
          , in: G. Gonthier, M. Norrish (Eds.),
          <source>Certified Programs and Proofs</source>
          , Springer International Publishing, Cham,
          <year>2013</year>
          , pp.
          <fpage>131</fpage>
          -
          <lpage>146</lpage>
          .
        </mixed-citation>
      </ref>
      <ref id="ref12">
        <mixed-citation>
          [12]
          <string-name>
            <given-names>A.</given-names>
            <surname>Asperti</surname>
          </string-name>
          ,
          <string-name>
            <given-names>W.</given-names>
            <surname>Ricciotti</surname>
          </string-name>
          ,
          <string-name>
            <given-names>C. S.</given-names>
            <surname>Coen</surname>
          </string-name>
          , E. Tassi, Hints in unification, in: Theorem Proving in Higher Order Logics, 22nd International Conference, TPHOLs
          <year>2009</year>
          , Munich, Germany,
          <source>August 17-20</source>
          ,
          <year>2009</year>
          . Proceedings,
          <year>2009</year>
          , pp.
          <fpage>84</fpage>
          -
          <lpage>98</lpage>
          . URL: https://doi.org/10.1007/ 978-3-
          <fpage>642</fpage>
          -03359-
          <issue>9</issue>
          _8. doi:
          <volume>10</volume>
          .1007/978-3-
          <fpage>642</fpage>
          -03359-9\_8.
        </mixed-citation>
      </ref>
      <ref id="ref13">
        <mixed-citation>
          [13]
          <string-name>
            <given-names>F.</given-names>
            <surname>Besson</surname>
          </string-name>
          ,
          <article-title>ppsimpl: a reflexive Coq tactic for canonising goals</article-title>
          ,
          <source>CoqPL</source>
          ,
          <year>2017</year>
          .
        </mixed-citation>
      </ref>
    </ref-list>
  </back>
</article>