Simplifying Casts and Coercions (Extended Abstract) Robert Y. Lewisa , Paul-Nicolas Madelaineb a Vrije Universiteit Amsterdam, The Netherlands b École Normale Supérieure, Paris, France Abstract This paper introduces norm_cast, a toolbox of tactics for the Lean proof assistant designed to manipu- late 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 transpar- ent as possible. They are used extensively to eliminate boilerplate arguments in the Lean mathematical library and in external developments. Keywords cast, coercion, Lean, formal proof 1. Introduction Many popular type-theoretic foundations for proof assistants, including the Calculus of Inductive Constructions, do not have native subtypes. Even for numeric types like N, Z, and Q with a natural chain of inclusions, terms must be cast from one to another with an explicit function application. The numeral 5 : N is syntactically different from 5 : Z and 5 : R. To construct the sum of variables n : N and z : Z, one needs either an unwieldy sum operator with type N → Z → Z or a way to lift n to the larger type Z. Inserting coercions is a common programming language feature, and proof assistants are no exception: many modern systems will interpret n + z in a reasonable way. Combined with type-polymorphic operations and relations like + and < and generic numeral expressions, subtyping concerns can largely be ignored at the input level. However, the ease of input often belies the complexity of the underlying term. Using such terms in practice can go against the grain of intuition, especially for users coming from mathematics, where one almost never makes such distinctions. It is frustrating to realize that work must be done to unify n < (5 : N) with ↑n < (5 : Z), where ↑n denotes the cast of n into Z. A more intricate example of this frustration appears in the Lean development of the 𝑝-adic numbers [1] while proving theorem of_int {p : N} (hp : prime p) (z : Z) : padic_norm p ↑z ≤ 1 where padic_norm : N → Q → Q. Straightforward steps reduce the proof to three goals: PAAR 2020: Seventh Workshop on Practical Aspects of Automated Reasoning, June 29–30, 2020, Paris, France (virtual) r.y.lewis@vu.nl (R. Y. Lewis); paul-nicolas.madelaine@ens.fr (P. Madelaine)  0000-0002-0877-7063 (R. Y. Lewis); 0000-0001-7116-9338 (P. Madelaine) © 2020 Copyright for this paper by its authors. Use permitted under Creative Commons License Attribution 4.0 International (CC BY 4.0). CEUR Workshop Proceedings http://ceur-ws.org ISSN 1613-0073 CEUR Workshop Proceedings (CEUR-WS.org) 53 Robert Y. Lewis et al. CEUR Workshop Proceedings 53–62 • prime p ⊢ (1 : Z) ≤ ↑p • z ̸= (0 : Z) ⊢ -padic_val_rat p ↑z ≤ (0 : Z) • z ̸= (0 : Z) ⊢ ↑z ̸= (0 : Q) 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], exact int.coe_nat_nonneg _ }, { exact int.cast_ne_zero.2 hz } We introduce a family of tactics implemented in the Lean proof assistant [2] 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 [3, 1]. It is incorporated into Lean’s mathematical library mathlib [4], where it is already invoked 221 times, and is also used heavily in external libraries [5]. 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], norm_cast; simp }, { exact_mod_cast hz } 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. 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 effective at dealing with these situations in practice. “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. 1 Lean users previously wrote a guide to managing casts by hand. This guide is archived with our supplementary materials. 2 https://lean-forward.github.io/norm_cast 54 Robert Y. Lewis et al. CEUR Workshop Proceedings 53–62 2. Lean Specifics 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. Lean’s elaborator inserts coercions using type classes [6, 7]. Its generic coercion function has signature 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 ↑. Type-polymorphic operators and relations like + and < 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. Lean’s powerful metaprogramming framework [8] 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. 3. Outline of the Simplification Procedure 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. As an example of the expected behavior, we simplify the expression ↑m + ↑n < (10 : Z), where m, n : N are cast to Z. The expected form of the output is m + n < (10 : N), in which no casts appear; recall that +, <, 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 < ↑(10 : N) 2. Factor ↑ to the outside of the left: ↑(m + n) < ↑(10 : N) 55 Robert Y. Lewis et al. CEUR Workshop Proceedings 53–62 3. Eliminate both casts to get an inequality over N: m + n < (10 : N) Steps 2 and 3 are effectively just applications of Lean’s simplification API with certain rewrite lemmas. Step 1 has a slightly different 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. 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 := . . . Our tool sorts these tagged declarations into three categories. • 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 < ↑b ↔ a < 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. 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 different 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). 4. Implementation The core simplification routine has type expr → tactic (expr × expr), taking in an ex- pression and returning it in simplified form with a proof that the output is equal to the input. 56 Robert Y. Lewis et al. CEUR Workshop Proceedings 53–62 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. 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. 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. 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. 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. squash if ℋ(lhs) ≥ 1, ℐ(lhs) = ℐ(rhs) = 0, and ℋ(lhs) > ℋ(rhs). When a lemma does not fit in any of these categories is tagged with the norm_cast attribute, an error is produced. 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. 57 Robert Y. Lewis et al. CEUR Workshop Proceedings 53–62 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 [9], it is much cleaner to automatically classify the rewrite rules, and we slightly redesigned the procedure to make this possible. 5. Interface 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. 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. 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. 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. 6. Examples The norm_cast test file3 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 < (5 : Q) ⊢ ↑n - z < (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) < ↑(5 : N). While both sides of the < are now casts to Q, the left comes from Z and the right from N, so no elim rule will fire; instead, 3 https://github.com/leanprover-community/mathlib/blob/master/test/norm_cast.lean 58 Robert Y. Lewis et al. CEUR Workshop Proceedings 53–62 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 < ↑b ↔ a < b to obtain ↑n - z < ↑(5 : N), and finally normalize the numeral on the right to (5 : Z). 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. example (α : Type) [linear_ordered_ring α] (n : N) (z : Z) (h : ↑n - ↑z < (5 : α)) : ↑n - z < (5 : Z) := by exact_mod_cast h Lean’s simplifier supports conditional rewriting, and norm_cast makes use of this support. Note that the following example does not hold when n > m, since m - n = 0. example (m n : N) (h : n ≤ m) : ↑(m - n) = (↑m - ↑n : Z) := by norm_cast 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𝑝 [1]. 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. 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) : 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 The proof originally written without norm_cast contains five explicit references to cast lemmas, and uses an explicit intermediate step that is unnecessary in the mathlib proof: have hp’ : (↑p : Q) ≥ 1, from show ↑p ≥ ↑(1 : N), from cast_le.2 (le_of_lt hp.gt_one) 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 a b : nnreal ⊢ ennreal.to_real ↑a ≤ ennreal.to_real ↑b ↔ ↑a ≤ ↑b the casts on the left are nnreal → ennreal; the goal is discharged immediately by norm_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 } 59 Robert Y. Lewis et al. CEUR Workshop Proceedings 53–62 Buzzard, Commelin, and Massot use norm_cast 53 times in their definition of a perfectoid space [5]. 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. 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. 7. Conclusion 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. Isabelle’s standard simplifier [10] 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 [11]. But it appears that approaches to managing and eliminating casts tend to be ad hoc combinations of simplification and manual work. In Coq, unification hints [12] can sometimes help to unify terms that differ 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 difficult to replicate the behavior of rw_mod_cast using computation without providing more explicit information. The ppsimpl preprocessing tool [13], which tries to eliminate inconvenient types and constants from a goal, shares some design features with norm_cast. 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. 60 Robert Y. Lewis et al. CEUR Workshop Proceedings 53–62 The tool is inherently coupled to its ambient library, meaning that it is only effective 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 different, procedural, kind of mathematical knowledge. 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. We acknowledge support from the European Research Council (ERC) under the European Union’s Horizon 2020 research and innovation program (grant agreement No. 713999, Ma- tryoshka) and from the Dutch Research Council (NWO) under the Vidi program (project No. 016.Vidi.189.037, Lean Forward). References [1] R. Y. Lewis, A formal proof of Hensel’s lemma over the 𝑝-adic integers, in: Proceedings of the 8th ACM SIGPLAN International Conference on Certified Programs and Proofs, CPP 2019, Cascais, Portugal, January 14-15, 2019, 2019, pp. 15–26. URL: https://doi.org/10.1145/ 3293880.3294089. doi:10.1145/3293880.3294089. [2] L. de Moura, S. Kong, J. Avigad, F. van Doorn, J. von Raumer, The Lean Theorem Prover (system description), in: A. P. Felty, A. Middeldorp (Eds.), CADE-25, Springer International Publishing, Cham, 2015, pp. 378–388. [3] S. R. Dahmen, J. Hölzl, R. Y. Lewis, Formalizing the solution to the cap set problem, in: J. Harrison, J. O’Leary, A. Tolmach (Eds.), 10th International Conference on Interactive Theorem Proving (ITP 2019), volume 141 of Leibniz International Proceedings in Informatics (LIPIcs), Schloss Dagstuhl–Leibniz-Zentrum fuer Informatik, Dagstuhl, Germany, 2019, pp. 15:1–15:19. doi:10.4230/LIPIcs.ITP.2019.15. [4] The mathlib Community, The Lean mathematical library, in: Proceedings of the 9th ACM SIGPLAN International Conference on Certified Programs and Proofs, CPP 2020, Association for Computing Machinery, New York, NY, USA, 2020, p. 367–381. URL: https: //doi.org/10.1145/3372885.3373824. doi:10.1145/3372885.3373824. [5] K. Buzzard, J. Commelin, P. Massot, Formalising perfectoid spaces, in: Proceedings of the 9th ACM SIGPLAN International Conference on Certified Programs and Proofs, CPP 2020, Association for Computing Machinery, New York, NY, USA, 2020, p. 299–312. URL: https://doi.org/10.1145/3372885.3373830. doi:10.1145/3372885.3373830. [6] D. Selsam, S. Ullrich, L. de Moura, Tabled typeclass resolution, 2020. arXiv:2001.04301. [7] P. Wadler, S. Blott, How to make ad-hoc polymorphism less ad-hoc, in: Conference Record of the Sixteenth Annual ACM Symposium on Principles of Programming Languages, Austin, Texas, USA, January 11-13, 1989, 1989, pp. 60–76. URL: https://doi.org/10.1145/75277.75283. doi:10.1145/75277.75283. [8] G. Ebner, S. Ullrich, J. Roesch, J. Avigad, L. de Moura, A metaprogramming framework for 61 Robert Y. Lewis et al. CEUR Workshop Proceedings 53–62 formal verification, PACMPL 1 (2017) 34:1–34:29. URL: https://doi.org/10.1145/3110278. doi:10.1145/3110278. [9] F. van Doorn, G. Ebner, R. Y. Lewis, Maintaining a library of formal mathematics, in: C. Benzmüller, B. Miller (Eds.), Intelligent Computer Mathematics, Springer International Publishing, Cham, 2020, pp. 251–267. [10] T. Nipkow, L. C. Paulson, M. Wenzel, Isabelle/HOL: a proof assistant for higher-order logic, volume 2283, Springer Science & Business Media, 2002. [11] B. Huffman, O. Kunčar, Lifting and Transfer: A modular design for quotients in Is- abelle/HOL, in: G. Gonthier, M. Norrish (Eds.), Certified Programs and Proofs, Springer International Publishing, Cham, 2013, pp. 131–146. [12] A. Asperti, W. Ricciotti, C. S. Coen, E. Tassi, Hints in unification, in: Theorem Proving in Higher Order Logics, 22nd International Conference, TPHOLs 2009, Munich, Ger- many, August 17-20, 2009. Proceedings, 2009, pp. 84–98. URL: https://doi.org/10.1007/ 978-3-642-03359-9_8. doi:10.1007/978-3-642-03359-9\_8. [13] F. Besson, ppsimpl: a reflexive Coq tactic for canonising goals, CoqPL, 2017. 62