Towards an Ostensibly Named Engine for the Mechanization of

advertisement
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
Download