• No results found

5.1 Overloading

5.1.1 Canonical Structures

To the best of our knowledge, the first application of term classes, that is, canonical structures keying on terms rather than just types, appeared in Bertot et al. (2008).

Term classes are used for encoding iterated versions of classes of algebraic operators.

Gonthier(2011) describes a library for matrix algebra in Coq, which introduces a variant of the tagging and lemma selection patterns (§2.2 and §2.5.2, respectively), but briefly, and as a relatively small part of a larger mathematical development. In contrast, in Chapter 2, we give a more abstract and detailed presentation of the general tagging pattern and explain its operation with a careful trace. We also present several other novel design patterns for canonical structures, and explore their use in reasoning about heap-manipulating programs.

Asperti et al. (2009) present unification hints, which generalize Coq’s canonical struc-tures by allowing a canonical solution to be declared for any class of unification equa-tions, not only for equations involving a projection out of a structure. Hints are shown to support applications similar to our reflection pattern from Section 2.4. However, they come with limitations; for example, the authors comment that hints cannot sup-port backtracking. Thus, we believe that the design patterns that we have developed in Chapter2 are not obviated by the additional generality of hints, and would be useful in that framework as well.

177

5.1.2 Type Classes

Sozeau and Oury (2008) present type classes for Coq, which are similar to canonical structures, but differ in a few important respects. The most salient difference is that inference for type class instances is not performed by unification, but by general proof search. This proof search is triggered after unification, and it is possible to give a weight to each instance to prioritize the search. This leads to somewhat simpler code, since no tagging is needed, but, on the other hand, it seems less expressive. For instance, we were not able to implement the search-and-replace pattern of Section2.5using Coq type classes, due to the lack of connection between proof search and unification. We were able to derive a different solution for bnd writeR using type classes, but the solution was more involved (requiring two specialized classes to differentiate the operations such as write which perform updates to specific heaps, from the operations which merely inspect pointers without performing updates). More importantly, we were not able to scale this solution to more advanced lemmas from our implementation of higher-order separation logic. In contrast, canonical structures did scale, and we provide the overloaded code for these lemmas in our source files (Ziliani,2014).

In the end, we managed to implement all the examples in this paper using Coq type classes, demonstrating that lemma overloading is a useful high-level concept and is not tied specifically to canonical structures. (The implementations using type classes are included in our source files as well (Ziliani, 2014).) Nevertheless, we have not yet arrived at a full understanding of how Coq type classes perform instance resolution. In particular, its semantics are hard to grasp, since type classes uses a completely unsound unification algorithm. In contrast, the semantics of canonical structures were thoroughly described in chapters 2and 4.

Besides this, ultimately, it may turn out that the two formalisms (canonical structures and type classes) are interchangeable in practice, but we need more experience with type classes to confirm this. In the future, we hope the unification algorithm presented in this thesis will be also used by the type class mechanism. This will add predictability to type classes, and allow us to frame the semantics of type classes in the same semantical framework.

Using Coq type classes,Spitters and van der Weegen(2011) present a reflection algorithm based on the example of Asperti et al. discussed above. In addition, they consider the use of type classes for overloading and inheritance when defining abstract mathematical structures such as rings and fields. They do not, however, consider lemma overloading more generally as a means of proof automation, as we have presented in Chapter 2.

Finally, in the context of Haskell,Morris and Jones(2010) propose an alternative design for a type class system, called ilab, which is based on the concept of instance chains.

Essentially, instance chains avoid the need for overlapping instances by allowing the programmer to control the order in which instances are considered during constraint resolution and to place conditions on when they may be considered. Our tagging pat-tern (Section2.2) can be seen as a way of coding up a restricted form of instance chains directly in existing Coq, instead of as a language extension, by relying on knowledge of how the Coq unification algorithm works. ilab also supports failure clauses, which en-able one to write instances that can only be applied if some constraint fails to hold. Our approach does not support anything directly analogous, although (as Morris and Jones mention) failure clauses can be encoded to some extent in terms of more heavyweight type class machinery.

5.2 Languages for Typechecked Tactics

In the last five years there has been increasing interest in languages that support safe tactics to manipulate proof terms of dependently typed logics. Delphin (Poswolsky and Sch¨urmann, 2009), Beluga (Cave and Pientka, 2012, Pientka, 2008, Pientka and Dunfield,2008), and VeriML (Stampoulis and Shao,2010,2012) are languages that, like Lemma Overloading (LO) and Mtac, fall into this category. By “safe” we mean that, if the execution of a tactic terminates, then the resulting proof term has the type specified by the tactic.

But, unlike LO and Mtac, these prior systems employ a strict separation of languages:

the computational language (the language used to write tactics) is completely different from the logical language (the language of proofs), making the meta-theory heavier than in our work. Indeed, our proof of type soundness is completely straightforward, as it inherits from CIC all the relevant properties such as type preservation under substitu-tion. Having a simple meta-theory is particularly important to avoid precluding future language extensions—indeed, extensions of the previous systems have often required a reworking of their meta-theory (Cave and Pientka,2012,Stampoulis and Shao,2012). In comparison, the extension of the Mtac language to support state (§3.6) did not require much rewriting of the original proof of soundness.

Another difference between these languages and ours is the logical language they support.

For Delphin and Beluga it is LF (Harper et al.,1993), for VeriML it is λHOL (Barendregt and Geuvers,2001), and for LO and Mtac it is CIC (Bertot and Cast´eran, 2004). CIC is the only one among these that provides support for computation at the term and type level, thereby enabling proofs by reflection (e.g., see §3.3.3). Instead, in previous

systems term reduction must be witnessed explicitly in proofs. To work around this, VeriML’s computational language includes a construct letstatic that allows one to stage the execution of tactics, so as to enable equational reasoning at typechecking time.

Then, proofs of (in-)equalities obtained from tactics can be directly injected in proof terms generated by tactics. This is similar to our use of run in the example from§3.3.3, with the caveat that letstatic cannot be used within definitions, as we did in the inner prod example, but rather only inside tactics.

In Beluga and VeriML the representation of objects of the logic in the computational language is based on Contextual Modal Type Theory (Nanevski et al.,2008c).1 There-fore, every object is annotated with the context in which it is immersed. For instance, a term t depending only on the variable x is written in Beluga as [x. t], and the type-checker enforces that t has only x free. In Mtac, it is only possible to perform this check dynamically, writing an Mtactic to inspect a term and rule out free variables not appearing in the set of allowed variables. (The interested reader may find an example of this Mtactic in the distributed files.) On the other hand, the syntax of the language and the meta-theory required to account for contextual objects are significantly heavier than those of Mtac.

Delphin shares with Mtac the νx : A binder from Nanevski (2002), Sch¨urmann et al.

(2005). In Delphin, the variable x introduced by this binder is distinguished with the type A#, in order to statically rule out offending terms like νx : A. ret x. In Mtac, instead, this check gets performed dynamically. Yet again, we see a tension between the simplicity of the meta-theory and the static guarantees provided by the system. In Mtac we favor the former.

Of all these systems, VeriML is the only one that provides ML-style references at the computational level. Our addition of mutable state to Mtac is clearly inspired by the work of Stampoulis and Shao (2010), although, as we do not work with Contextual Modal Type Theory, we are able to keep the meta-theory of references quite simple.

Beluga’s typechecker is constantly growing in complexity in order to statically verify the completeness and correctness of tactics (through coverage and termination checking). If a tactic is proved to cover all possible shapes of the inspected object, and to be termi-nating, then there is no reason to execute it: it is itself a meta-theorem one can trust.

This concept, also discussed below, represents an interesting area of future research for Mtac.

1The contextual types of CMTT are not to be confused with the lightweight “contextual types” that LO and Mtac assigns to unification variables (e.g.,§3.4.2). In our setting, we only use contextual types to ensure soundness of unification, inheriting the mechanism from Coq. Coq’s contextual types are essentially hidden from the user, whereas in VeriML and Beluga they are explicit in the computational language.