Towards an Ostensibly Named Engine for the Mechanization of Binding Wilmer Ricciotti Benedikt Ahrens IRIT / Université de Toulouse {Wilmer.Ricciotti,Benedikt.Ahrens}@irit.fr Abstract One of the most important choices in the formalization of programming languages in theorem provers is the representation of binding. Many concrete styles have been suggested, each one coming with a specific trade-off; furthermore, they are all considerably distant from informal logical practice. To bridge the gap between informal notation and formal implementation, a formalization style based on an ostensibly named abstract data type has recently been proposed by Ricciotti and used in a formalization of the simply typed lambda calculus. In this paper we extend that idea and propose a research program aiming at an implementation-independent framework for the mechanization of languages with binding structures. To this end, we analyze the techniques used in Ricciotti’s case study in order to generalize them to larger classes of languages. 1 Ostensibly named syntax When formalizing programming languages with variable binding constructions, one question one is faced with is how to represent variable binding constructors such as the lambda abstraction of functional programming. Various techniques for the representation of such binders have been studied, e.g., nominal syntax, higher-order abstract syntax or nested datatypes. Giving a comprehensive overview of the literature is a hopeless undertaking; we refrain from any attempt at doing so, and point instead to the POPLmark challenge [6], which collects a variety of formalizations of syntax. To summarize the outcome of this challenge, each technique for representing binders comes with its advantages and inconveniences, and no clear winner has emerged yet. The ostensibly named approach to the formalization of binding proposed by Ricciotti in [12] follows a trend in the separation of notation and concrete encoding already seen in previous work (e.g. [9]) and combines a canonical (possibly nameless) internal encoding of terms with a high-level interface whose notation employs named bound variables as in informal mathematics. Thanks to the well-behaved operations provided by the interface, the proof style is kept as 1 close as possible to that of informal proofs. Access to the internal representation is prevented by treating the type of object terms as abstract. Lam x (Lam y (App (Par y) (Par x))) (a) abs (abs (app (var 0) (var 1))) (b) % λx.λy.y x & Figure 1: Named notation (a) and nameless internal encoding (b) of a term of the lambda calculus. More concretely, an ostensibly named axiomatization is a module providing the following interface: (a) a set of abstract constants, declaring a type of object terms, its constructors and recursion operator; (b) a quasi-equational theory of terms, i.e. axioms expressing (among other things) α-equivalence and computation rules for the recursion operator. One of the features of ON syntax is that not only terms (of type Λ0 ) but also contexts with i holes (of type Λi ) are defined: given a context u in Λi+1 , we can apply it to a free parameter x and obtain udxe of type Λi . Dually, given an i-ary context u and a parameter x, the ν operator allows us to replace all occurrences of x with a new hole, obtaining an (i + 1)-ary context νx.u. Then, the renaming which replaces x with y in a term or context u can be expressed in terms of these operations as u hy/xi = (νx.u)dye. Figure 2 presents a selection of the constants and axioms needed for the lambda calculus: notice that the computation rule for recursion under lambdas is expressed as a constrained rewriting principle, in order for the name of the bound variable not to be exposed. It is possible to use such an axiomatization to carry out proofs about the metatheory of the language: however, working with abstract data types in a proof assistant is less natural than working with concrete types. 2 Signatures for ostensibly named syntax In the previous section we analyzed the ostensibly named axiomatization of a specific language. In this section, we explain how to generalize those techniques to a whole class of languages. To generalize from a specific language to a class of languages, one needs to introduce a notion of signature. A signature abstractly specifies a language, containing all the necessary information about the language constructors, their arguments, bound variables etc. Pottier [11] gives a good overview of what information should be contained in a signature—a specification language in his 2 Terms and (Λi )i∈N : Par : App : Lam : contexts: Type A ⇒ Λ0 Λ0 ⇒ Λ0 ⇒ Λ0 A ⇒ Λ0 ⇒ Λ0 ν : A ⇒ Λi ⇒ Λi+1 −d−e : Λi+1 ⇒ A ⇒ Λi The FV operation returning free names in a term/context, and its equational theory. The equational theory of ν and −d−e. Recursion principle on terms RΛ0 and its quasi equational theory (excerpt). Writing R∗ for (RΛ0 T C fPar fApp fLam ): R∗ (Par X) = fPar X R∗ (App u v) = fApp u v (R∗ u) (R∗ v) ∀y, Hy . P (udye) (fLam C y u Hy (R∗ (udye))) (x ∈ / FV(u)) P (Lam x (udxe) (R∗ (Lam x (udxe))) Figure 2: Ostensibly named axiomatization of the lambda calculus. vocabulary. In particular, it should provide information about (1) the number of language constructors; (2) the number and type of subterms of each such constructor; (3) the number and type of variables bound in each constructor. 2.1 External vs. internal signatures When formalizing the theory of a class of languages specified by a notion of signature in a proof assistant, there are two choices: the definition of signature may be either external or internal to the proof assistant. Examples of external usage of signatures are given by the tools DBGen [10] and Ott [14]. There, signatures are parsed by an external tool (e.g., an OCaml parser) and for each signature, a theory file (e.g., for use in Coq) is produced, proving facts about the specific signature in question. This approach usually makes extensive use of automation. On the other hand, signatures may be defined internally, as in, e.g., [1, 2]. In this approach, theorems are proved about an arbitrary signature which parametrizes the theorem. Afterwards, theorems are instantiated within the proof assistant. This approach is the one to use when proving theorems about a specific class of languages/signatures, and it is the one we adopt for our work on ON syntax. 3 2.2 Restricting to untyped signatures Pottier’s list [11] of desiderata for signatures is long—too long for us. In an attempt to separate concerns, we work with a restricted class of languages, given by a very simple notion of signatures. The languages we consider are untyped. Definition 2.1. An arity is a list of natural numbers a ∈ list N. A signature is given by a family of arities, that is, by a set A and a map A → list N. Intuitively, the length of an arity specifies the number of arguments of its associated language constructor, and the indices of the arity specify the number of variables bound in the corresponding argument. We choose the “variablesas-terms” arity not to be explicitly part of a signature; instead, we consider it to always be there. It will also be treated specially when defining the interface associated to a signature. Example 2.2. The signature of the lambda calculus is given by A = {abs, app} and abs 7→ [1], app 7→ [0, 0]. The lambda calculus with “let-in” has an additional arity letin 7→ [0, 1]. An arity specifies the type of its associated constructor—this is particularly easy when using the nested datatype approach to variable binding (as in [1]). There, the lambda abstraction of Example 2.2 has type abs : LC(option1 (X)) → LC(X), where the “1” in the exponent is given by the list [1]. For app, we have option0 (X) := X. Using a named approach to syntax as in ONE, the translation from an arity into a type is more involved: Definition 2.3. Given an arity ` ∈ list N, the domain of its associated constructor is given by ( 1 if ` = [] cdom(`, T ) := n 0 (A × T ) × cdom(` , T ) if ` = n :: `0 The interface we associate to a signature consists of: 1. a type of “terms with holes”, that is, a family T : N → Type; 2. maps of suitable type for each arity, ` : cdom(`, (T 0)) → (T 0) 3. a recursion principle, the type of which we omit here because of space constraints, and its computation rules; 4. some administrative axioms about free variables and the manipulation of contexts. Conjecture 2.4. For any signature S as in Definition 2.1, its interface can be instantiated. That is, the interface is consistent. 4 3 Tool support for ostensibly named syntax In this section, we discuss some common issues in ostensibly named formalizations and propose enhancements that would make them more natural. We plan to implement such enhancements in the Matita theorem prover [3]. 3.1 Syntactic sugar The recursion principle of an ostensibly named ADT has a twofold purpose: it allows one to define functions on terms with binding structures by primitive recursion, and to perform case analysis. Clearly, case analysis is a trivial case of primitive recursion where the result of recursive calls is ignored. From the user perspective, most algorithms are not expressed by means of recursion principles (except some canonical idioms that occur, especially, with lists), because the resulting code is innatural and unreadable. The user wants to write algorithms by means of generalized recursion (also known as fixpoints or let recs) and pattern matching, knowing that the theorem prover will then verify that the function is total by means of some syntactic heuristic. Thus, we want to support syntactic sugar for both pattern matching and recursion over ON syntax. The existing Matita facilities for user-defined syntax are sufficiently expressive to allow us to parse pattern matching and interpret it as an instance of a recursion principle. On the other hand, handling general let recs is not as simple, both for theoretical reasons and technical limits of Matita. The theoretical reasons are related to the fact that while termination of general recursive functions is checked by a heuristic, the termination of expressions involving the recursion principle is trivial (since we are composing expressions that are themselves terminating, under the typing rules of a formalism like CIC): thus, it is not possible in general to convert the first into the second in a mechanized way. The best we can do is to restrict ourselves to a certain class of recursive functions: as a basic scenario, we consider those functions that can be expressed as follows: − let rec f u → y := match u with [Par x ⇒ ePar [u] → − − |App u1 u2 ⇒ eApp [u, f u1 → g , f u2 g 0 ] − → |Lam x (u0 dxe) (H : x ∈ / FV(u0 ), C) ⇒ eLam [u, f (u0 dxe) g 00 ] ] where the syntax e[e0 ] denotes an expression e where the subexpression e0 occurs syntactically. This syntax should be interpreted by the theorem prover as − RΛ0 ?T C (λ→ y , x.ePar [Par x]) → − − − (λ→ y , u1 , u2 , r1 , r2 .eApp [App u1 u2 , r1 → g , r2 g 0 ]) − → − − (λ→ y , x, u , H, r .e [Lam x (udxe)→ y , r g 00 ]) 0 0 Lam 0 Here, ?T is a metavariable representing the return type of the pattern matching, which will in most frequent cases be automatically instantiated by type-inference 5 and, as such, does not pose any particular problem; still, this kind of interpretation cannot currently be expressed in Matita. The reason is that the parser must be capable of matching and replacing certain subexpressions in the branches of the case analysis: the matched term u must be changed to its value in each branch (either Par x, App u1 u2 , or Lam x (udxe)), and the recursive calls to f should be replaced with the variable representing the result of the recursive call (r1 , r2 or r0 ). In order to support this kind of syntax, we will have to implement the necessary modifications to Matita’s syntax extension mechanism. 3.2 Tactic support Simplification The usual reduction tactics performing computation as it is defined in the proof assistant’s logic are of no use with an ON recursion principle, since such a principle is seen by the logic as an opaque constant. Reduction on RΛ0 is simulated by means of sequences of rewritings using the equational theory of the principle. The simplification tactic that a formalizer would like to use should be able to mix equational reasoning with reduction steps, so that he will not have to worry whether a certain function is defined by means of RΛ0 or as a true function of the logic. Matita’s effective superposition automation [5] will be employed to perform all the simplification by equational reasoning; as for the full simplification tactic, there are two options: • repeat normalization and simplification by superposition alternatively, until the expression to be simplified reaches a stable shape • implement, at the tactic level, an extended version of the CIC reduction machine used by Matita ([4]) that is capable of understanding RΛ0 . The second option appears to be theoretically more robust and appealing, but it is also unable to reuse the existing code. Only experiments can hint at which solution is more practical. Management of names Unsurprisingly, the management of names is one of the areas where we expect more assistance from a theorem prover. A first, very simple tactic that should be provided, allows the formalizer to generate and add to the context of the goal a new fresh name y together with its proof of freshness Hy by means of a human readable syntax like let y ∈ / FV(u) as Hy where the current Matita syntax for the same operation is lapply (p fresh . . . (FV(u)) lapply (N fresh . . . (FV(u)) #y #Hy 6 This is an important, albeit cosmetic, improvement. However, there are more complex problems related to proofs of freshness. Statements that, like the Hy generated by the previous example, are in the form x ∈ ` or x ∈ / `, where x is a name and ` is a list of names, are extremely frequent in both hypotheses and goals. Proving that a name is, or is not, in a list under a set of assumptions of the same shape can look trivial, however basic automation is often of little use and the formalized proof can take, in certain cases, several lines. For instance, suppose we have to prove the following goal: x ∈ FV(Lam y (App (Par z) (Lam w (Par y)))) y ∈ FV(Lam z (Par y)) Informally, we can argue that x must be different from y since the latter is bound by a Lam and that, therefore, it must be equal to the only other name appearing as the argument of a Par. From x 6= y and x = z, the thesis follows easily. However, in a formal setting, we will have to go through the following intermediate results: (1) x 6= y ∧ x ∈ FV(App (P ar z) (Lam w (Par y))) (2) x ∈ FV(Par z) ∨ x ∈ FV(Lam w (Par y)) • if x ∈ FV(Par z), then (i) x = z (ii) from this and (1) we can prove y 6= z ∧ y ∈ FV(Par y) (iii) consequently, y ∈ FV(Lam z (Par y)) • if, on the other hand, x ∈ FV(Lam w (Par y)), then (i) x 6= w ∧ x ∈ FV(Par y) (ii) x = y (iii) this is absurd by (1) What is particularly upsetting in this proof is that we have to consider several cases, some of which are vacuous. In the most general scenario, we have to decompose the goal and the relevant hypotheses as nested conjunctions and disjunctions of literals asserting that a name is, or is not, in a given list. Not surprisingly, there is space for automation: since the property of being in a list of names is decidable, we can restate each of the relevant propositions in, say, conjunctive normal form and prove the goal by resolution [7]. Alpha-renaming Alpha-renaming is not a trivial task. For starters, it is not always allowed: if, given a term Lam x u, you want to change the bound x to y and obtain Lam y (u hy/xi), you first have to make sure that y is not free in the original term: i.e., you have to provide a proof that y ∈ / FV(Lam x u). Secondarily, a feature of ON syntax is that binders often occur in focused contextual form, i.e. Lam x (vdxe) where x ∈ / FV(v). In such a case, renaming 7 x to y should yield Lam y (vdye) rather than the equivalent, but unnecessarily complex Lam y (vdxe hy/xi) (of course, after checking that x, y ∈ / FV(v)). The user should be able to perform the renaming on both focused and unfocused binders by means of the same syntax: rename x as y in path where path identifies an occurrence of the term where the renaming should take place. To perform the actual renaming, we can exploit the automation on simplification and name management described in the previous paragraphs. If a certain freshness condition cannot be verified automatically, the theorem prover should open a new subgoal, asking users to provide the proof of freshness themselves. Unification of constructor forms Unification of constructor forms is one of the facilities offered by most proof assistants on inductive types: it will allow the user to automatically derive certain properties on (in)equalities involving constructors like Par x = Par y → x = y or App u1 u2 6= Lam x v. Such properties are often derived as corollaries of a discrimination lemma in a style popularized by McBride [8]. In an ostensibly named setting, the lemma assumes the form: lemma tm discr : ∀u, v : tm.u = v → match (u, v) with [(Par x, Par y) |(App u1 u2 , App v1 v2 ) |(Lam x (u0 dxe), Lam y (v0 dye)) ( :x∈ / FV(u0 )) ( : y ∈ / FV(v0 )) |( , ) ⇒ ∀P.(x = y → P ) → P ⇒ ∀P.(u1 = v1 → u2 = v2 → P ) → P ⇒ ∀P.(u0 dxe = v0 dxe → P ) → P ⇒ ∀P.P ] This is very similar to discrimination lemmata on concrete inductive types, with an important difference concerning bound variables, here represented by the Lam − Lam case (third branch of the case analysis): if Lam x (u0 dxe) = Lam y (v0 dye), we cannot deduce x = y. Instead, we deduce that we can use α-renaming on one of the two terms so that the bound variable will be the same – for instance, by choosing x over y – and consequently deduce that u0 dxe = v0 dxe. This special treatment of constructor arguments representing bound variables must be integrated into the unification tactic in order for it to be able to work on unfocused terms, e.g. to prove Lam x u = Lam y v implies u = v hx/yi. Notice however that this derived property involves a term (v hx/yi) which is not structurally smaller than the original Lam y v (a renaming has been introduced), thus raising termination and completeness concerns. 8 4 Research plan In the previous sections, we have presented the key ingredients of the ostensibly named engine (for short: ONE) that we are starting to implement for Matita (Fig. 3). To be more concrete, these ingredients must be supported by extending the layer of Matita that sits between logic-dependent parts (the implementation of type theory and the refiner) and user-interface parts. In particular we have to provide: • a formalization of signatures, with their associated interface and concrete implementation; additional facilities, such as a substitution and a discrimination principle, should also be derived from the generalized interface once and for all; • enhancements to the syntax extension facilities providing the mechanism discussed in Section 3.1; • new specialized tactics for simplification, name management, alpha-renaming, and unification of constructor forms. The class of signatures described in Section 2 is very restrictive, and will ultimately need to be generalized; however, it provides a good starting point for an immediate implementation of the aforementioned components. In this setting, we estimate the implementation of notational enhancements and specialized tactics to require about 6 months-person. For what concerns the formalization of signatures, we must distinguish the interface from the implementation and the derived definitions and proofs: while formalizing the interface is a simple task requiring less than one month-person of work (which is being completed at the time of writing [13]), the rest is feasible, but difficult to estimate. The experience with this class of untyped signatures will give us the insight needed to extend to more complicated forms of binding structures: these should include, at a minimum, variable-arity binders and typing judgments. Human User interface Notation Tactics Library Formalization of Signatures Refiner Kernel Figure 3: Ostensibly named engine in the architecture of Matita 9 References [1] Benedikt Ahrens (2015): Modules over relative monads for syntax and semantics. Mathematical Structures in Computer Science FirstView, pp. 1–35, doi:10.1017/S0960129514000103. [2] Benedikt Ahrens & Julianna Zsido (2011): Initial Semantics for higherorder typed syntax in Coq. J. Formalized Reasoning 4(1), pp. 25–69, doi:10.6092/issn.1972-5787/2066. [3] Andrea Asperti, Wilmer Ricciotti, Claudio Sacerdoti Coen & Enrico Tassi (2011): The Matita Interactive Theorem Prover. In: Proceedings of CADE 2011, LNCS 6803, doi:10.1007/978-3-642-22438-6 7. [4] Andrea Asperti, Wilmer Ricciotti, Claudio Sacerdoti Coen & Enrico Tassi (2009): A compact kernel for the Calculus of Inductive Constructions. Sadhana 34(1), pp. 71–144, doi:10.1007/s12046-009-0003-3. [5] Andrea Asperti & Enrico Tassi (2011): Superposition as a logical glue. EPTCS 53, pp. 1–15, doi:10.4204/EPTCS.53.1. [6] B. E. Aydemir, A. Bohannon, M. Fairbairn, J. Nathan Foster, B. C. Pierce, P. Sewell, D. Vytiniotis, G. Washburn, S. Weirich & S. Zdancewic (2005): Mechanized metatheory for the masses: The POPLmark Challenge. In J. Hurd & T. Melham, editors: Proceedings of TPHOLs, LNCS 3603, pp. 50–65, doi:10.1007/11541868 4. [7] Marc Bezem, Dimitri Hendriks & Hans de Nivelle (2002): Automated Proof Construction in Type Theory Using Resolution. J. Autom. Reasoning 29(34), pp. 253–275, doi:10.1007/1072195i 10. [8] Conor McBride (1999): Dependently Typed Functional Programs and their Proofs. Ph.D. thesis, University of Edinburgh. [9] Randy Pollack, Masahiko Sato & Wilmer Ricciotti (2012): A Canonical Locally Named Representation of Binding. Journal of Automated Reasoning 49(2), pp. 185–207, doi:10.1007/s10817-011-9229-y. [10] Emmanuel Polonowski (2013): Automatically Generated Infrastructure for De Bruijn Syntaxes. In: Proceedings of ITP 2013, Rennes, France, LNCS 7998, Springer, pp. 402–417, doi:10.1007/978-3-642-39634-2 29. [11] François Pottier (2006): An Overview of C αml. Electr. Notes Theor. Comput. Sci. 148(2), pp. 27–52, doi:10.1016/j.entcs.2005.11.039. [12] Wilmer Ricciotti (2015): Binding Structures as an Abstract Data Type. In J. Vitek, editor: Proceedings of ESOP, part of ETAPS 2015, LNCS 9032, Springer, pp. 762–786, doi:10.1007/978-3-662-46669-8 31. 10 [13] Wilmer Ricciotti & Benedikt Ahrens: signatures: formalization in Matita. https://github.com/barolobaron/one.git. Ostensibly named Available at [14] Peter Sewell, Francesco Zappa Nardelli, Scott Owens, Gilles Peskine, Thomas Ridge, Susmit Sarkar & Rok Strnisa (2010): Ott: Effective tool support for the working semanticist. J. Funct. Program. 20(1), pp. 71–122, doi:10.1017/S0956796809990293. 11