Academia.eduAcademia.edu

Syntactic Theories in Practice

2001, Electronic Notes in Theoretical Computer Science

The evaluation function of a syntactic theory is canonically defined as the transitive closure of (1) decomposing a program into an evaluation context and a redex, (2) contracting this redex, and (3) plugging the contractum in the context. Directly implementing this evaluation function therefore yields an interpreter with a worst-case overhead, for each step, that is linear in the size of the input program. We present sufficient conditions over a syntactic theory to circumvent this overhead, by replacing the composition of (3) plugging and (1) decomposing by a single "refocusing" function mapping a contractum and a context into a new context and a new redex, if there are any. We also show how to construct such a refocusing function, we prove its correctness, and we analyze its complexity.

BRICS Basic Research in Computer Science BRICS RS-02-4 Danvy & Nielsen: Syntactic Theories in Practice Syntactic Theories in Practice Olivier Danvy Lasse R. Nielsen BRICS Report Series ISSN 0909-0878 RS-02-4 January 2002 Copyright c 2002, Olivier Danvy & Lasse R. Nielsen. BRICS, Department of Computer Science University of Aarhus. All rights reserved. Reproduction of all or part of this work is permitted for educational or research use on condition that this copyright notice is included in any copy. See back inner page for a list of recent BRICS Report Series publications. Copies may be obtained by contacting: BRICS Department of Computer Science University of Aarhus Ny Munkegade, building 540 DK–8000 Aarhus C Denmark Telephone: +45 8942 3360 Telefax: +45 8942 3255 Internet: [email protected] BRICS publications are in general accessible through the World Wide Web and anonymous FTP through these URLs: http://www.brics.dk ftp://ftp.brics.dk This document in subdirectory RS/02/4/ Syntactic Theories in Practice ∗ Olivier Danvy and Lasse R. Nielsen BRICS † Department of Computer Science University of Aarhus ‡ January 31, 2002 Abstract The evaluation function of a syntactic theory is canonically defined as the transitive closure of (1) decomposing a program into an evaluation context and a redex, (2) contracting this redex, and (3) plugging the contractum in the context. Directly implementing this evaluation function therefore yields an interpreter with a worst-case overhead, for each step, that is linear in the size of the input program. We present sufficient conditions over a syntactic theory to circumvent this overhead, by replacing the composition of (3) plugging and (1) decomposing by a single “refocusing” function mapping a contractum and a context into a new context and a new redex, if there are any. We also show how to construct such a refocusing function, we prove its correctness, and we analyze its complexity. We illustrate refocusing with two examples: a programming-language interpreter and a transformation into continuation-passing style. As a byproduct, the time complexity of this program transformation is mechanically changed from quadratic in the worst case to linear. Keywords: syntactic theories, interpreters, refocusing. ∗A preliminary version of this work was presented at RULE 2001 [4, 10]. Research in Computer Science (www.brics.dk), funded by the Danish National Research Foundation. ‡ Ny Munkegade, Building 540, DK-8000 Aarhus C, Denmark E-mail: {danvy,lrn}@brics.dk † Basic 1 Contents 1 Introduction 1.1 Related work . . . . . . . . . . . . . . 1.2 Interpreters for syntactic theories . . . 1.3 A canonical example: the call-by-value 1.4 This work . . . . . . . . . . . . . . . . . . . . . . . . . . . . λ-calculus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 3 3 4 2 The call-by-value λ-calculus 2.1 An interpreter . . . . . . . . . . . . 2.1.1 The original specification . 2.1.2 The refocused specification 2.2 A CPS transformer . . . . . . . . . 2.2.1 The original specification . 2.2.2 The refocused specification 2.3 Refocusing efficiently . . . . . . . . 2.3.1 The interpreter . . . . . . . 2.3.2 The CPS transformer . . . 2.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 5 5 6 6 6 7 8 9 9 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Refocusing in a syntactic theory 3.1 The language . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Syntactic theories . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.1 Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.2 Evaluation contexts . . . . . . . . . . . . . . . . . . . . . 3.2.3 Decompositions and potential redexes . . . . . . . . . . . 3.2.4 Unique decomposition . . . . . . . . . . . . . . . . . . . . 3.2.5 Reduction rules . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1 No redundant constructs . . . . . . . . . . . . . . . . . . . 3.3.2 Distinct value constructors and potential-redex constructors 3.3.3 Left-to-right evaluation of sub-terms . . . . . . . . . . . . 3.4 Consequences of the requirements . . . . . . . . . . . . . . . . . . 3.5 Constructing a refocus function . . . . . . . . . . . . . . . . . . . 3.6 Correctness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.7 Complexity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 10 10 10 10 11 11 12 12 12 13 13 13 15 17 19 19 4 Conclusion and issues 20 A Arithmetic expressions with precedence A.1 A syntactic theory . . . . . . . . . . . . . . A.2 Evaluation contexts with right-composition A.3 Implementation . . . . . . . . . . . . . . . . A.4 Refocusing . . . . . . . . . . . . . . . . . . . A.5 Summary . . . . . . . . . . . . . . . . . . . 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 20 22 23 27 33 1 Introduction A syntactic theory provides a uniform, concise, and elegant framework for specifying the semantics of a programming language and reasoning about programs [5, 6, 7]. We consider the issue of implementing the evaluation function of a syntactic theory in the form of an interpreter. Our emphasis, however, is not on automating this process, as in Xiao and Ariola’s SL project [17, 18].1 Instead, we identify that the direct implementation of an evaluation function entails an overhead and we show how to circumvent it. 1.1 Related work Formal semantics has always offered a tempting format for implementing specifications or even for designing programming languages—witness denotational semantics and functional programming [13], Moggi’s computational monads and monad-based functional programming [15], operational semantics and logic programming [9], etc. Formal semantics can also provide cost models for computations [8]. The present article describes how to bypass the cost of consecutive plug-and-decompose operations when implementing an interpreter for a syntactic theory, an issue that is usually dealt with on a case-by-case basis to construct abstract machines [6, Chapter 7]. 1.2 Interpreters for syntactic theories A syntactic theory is a small-step operational semantics where evaluation is defined as the transitive closure of single reductions, each performed by (1) decomposing a program into a context and a potential redex, (2) contracting the redex, if possible,2 and (3) plugging the contractum in the context. Most syntactic theories are developed for deterministic programming languages and satisfy a unique-decomposition property. The interpreter for a syntactic theory implements its evaluation function. It naturally consists of a decompose-contract-plug loop. Decomposition is usually implemented with a depth-first search in the abstract syntax tree. The decompose step therefore introduces a significant overhead, proportional to the program size. Likewise, plugging takes time linear in the size of the context and it always takes at most as long as the following decomposition, if there is one. 1.3 A canonical example: the call-by-value λ-calculus Here is a syntactic theory of the call-by-value λ-calculus: e ::= x | λx.e | e e v ::= x | λx.e e∈Λ v ∈ Value ⊆ Λ x ∈ Var 1 http://www.cs.uoregon.edu/~ariola/SL/ 2 Some potential redexes are not actual ones—they are stuck terms [11]. 3 E ::= [ ] | E[[ ] e] | E[v [ ]] r ::= v v E ∈ EvCont r ∈ PotRedex ⊆ Λ Among potential redexes, only β-redexes are actual redexes. Therefore, the only reduction rule is the following one: E[(λx.e) v] → E[e[v/x]] Plugging the hole of an evaluation context with an expression is defined by induction on the evaluation context, and thus it operates in time proportional to the depth of the context: ([ ])[e] (E[[ ] e′ ])[e] (E[v [ ]])[e] = e = E[e e′ ] = E[v e] Likewise, decomposition operates in time proportional, in the worst case, to the size of the term to decompose. Let us illustrate this overhead with Church numerals: the Church numeral for the number n is λs.λz. s(s(s(. . . (s z) . . .))) and is noted ⌈n⌉. | {z } n Example 1 We consider the term ⌈n⌉ (λx.x) v where v is any value. This term reduces in two steps to (λx.x)((λx.x)((λx.x)(. . . ((λx.x) v) . . .))). From then on, {z } | n each decomposition into a context and a redex (where the redex is always (λx.x)v) takes time proportional to the number of remaining applications. The total time taken by decomposition during evaluation is thus at least proportional to n2 .  1.4 This work We state conditions under which consecutive plug-and-decompose operations can be replaced by a more efficient refocus operation. These conditions pertain to the order in which a redex occurs, in the depth-first traversal of the decompose operation. We show that these conditions hold if the syntactic theory is given in the “standard” way, i.e., by a context-free grammar of values and evaluation contexts, and if it satisfies a unique-decomposition property. We also show how to mechanically construct such a refocus function. The rest of this article is structured as follows. We first illustrate refocusing with two concrete examples: a syntactic theory for the call-by-value λ-calculus and a transformation of call-by-value λ-terms into continuation-passing style due to Sabry and Felleisen [12]. We then list sufficient conditions to refocus a syntactic theory. The proof that these conditions are sufficient is constructive. It indicates how to mechanically rephrase the syntactic theory to circumvent the overhead. These conditions are general enough to make refocusing practically useful—for example, they are fulfilled by all the examples documented in the SL project [17, 18]. 4 2 The call-by-value λ-calculus Let us go back to the call-by-value λ-calculus, initially considered in Section 1.3. First, we uniformly restate the grammars of the language and of the syntactic theory in the format used in the rest of this article. These grammars can be expressed directly as ML data types and the corresponding functions as ML functions. e ::= var(x) | lam(x, e) | app(e, e) v ::= var(x) | lam(x, e) E ::= [ ] | E[app([ ], e)] | E[app(v, [ ])] r ::= app(v, v) e v x E r ∈ ∈ ∈ ∈ ∈ Exp Val Var EvCont PotRedex Var is the syntactic category of variables. We make decomposition and plugging explicit with two functions: decompose : Exp → Val + (EvCont × PotRedex) plug : Exp × EvCont → Exp We define the refocusing function refocus as the composition of decompose and plug. Its type is thus as follows. refocus : Exp × EvCont → Val + (EvCont × PotRedex) We now consider an interpreter and then a CPS transformer for the call-byvalue λ-calculus (Sections 2.1 and 2.2). Each uses decompose and plug, which we fuse into refocus (Section 2.3). 2.1 2.1.1 An interpreter The original specification The following interpreter implements the evaluation function of the syntactic theory. It takes one expression as argument and repeatedly decomposes, contracts, and plugs until either a value or a stuck term is reached, if any. eval : Exp → Val + (EvCont × PotRedex) eval (e) = eval ′ (decompose(e)) eval ′ : Val + (EvCont × PotRedex) eval ′ (v) eval ′ (E, app(lam(x, e), v)) eval ′ (E, app(x, v)) → = = = Val + (EvCont × PotRedex) v eval (plug(e[v/x], E)) (E, app(x, v)) The evaluation of an expression e is eval (e). (NB: The last rule of eval ′ is used for stuck terms.) The interpreter contains two calls to eval : one in the second rule of eval ′ and one in the initial call. In the second rule of eval ′ , the argument of eval is the 5 result of plug. In some sense, so is it too in the initial call, since e = plug(e, [ ]). In that sense, decompose (in the definition of eval ) is always called with the result of plug. Let us re-express the interpreter with a refocusing function that combines the effects of decompose and plug. 2.1.2 The refocused specification We change the interpreter to use refocus instead of decompose and plug. eval : Exp → Val + (EvCont × PotRedex) eval (e) = eval ′ (refocus(e, [ ])) eval ′ : Val + (EvCont × PotRedex) eval ′ (v) eval ′ (E, app(lam(x, e), v)) eval ′ (E, app(x, v)) → = = = Val + (EvCont × PotRedex) v eval ′ (refocus(e[v/x], E)) (E, app(x, v)) The evaluation of an expression e is eval (e). We are now free to use any implementation of refocus that is extensionally equivalent to decompose ◦ plug. 2.2 A CPS transformer In their work on reasoning about programs in continuation-passing style (CPS), Sabry and Felleisen designed a new CPS transformation [12, Definition 5]. This CPS transformation integrates a notion of generalized reduction and thus yields very compact CPS programs [3]. It is also unusual in the sense that it builds on the notion of a syntactic theory, rather than on operational semantics [2, 11] or denotational semantics [16]. Therefore, it is defined as the transitive closure of decomposing, performing an elementary CPS transformation, and plugging, which makes it a candidate for refocusing. 2.2.1 The original specification Definition 1 (Sabry and Felleisen, 1993) The following CPS transformation uses three mutually recursive functions: Ck (and the auxiliary function Ck′ ) to transform expressions, Φ to transform values, and Kk to transform evaluation contexts. The functions Ck , Ck′ , and Kk are parameterized over a variable k that represents the current continuation. Ck : Exp → Exp Ck (e) = Ck′ (decompose(e)) Ck′ : Val + (EvCont × PotRedex) Ck′ (v) Ck (E, app(x, v)) Ck (E, app(lam(x, e), v)) 6 → = = = Exp app(k, Φ(v)) app(app(x, Kk (E)), Φ(v)) app(lam(x, Ck (plug (e, E))), Φ(v)) Φ : Val → Exp Φ(x) = x Φ(lam(x, e)) = lam(k, lam(x, Ck (e))) where k is fresh Kk : EvCont Kk ([ ]) Kk (E[app(x, [ ])]) Kk (E[app(lam(x, e), [ ])]) Kk (E[app([ ], e)]) → = = = = Exp k app(x, Kk (E)) lam(x, Ck (plug(e, E))) lam(ui , Ck (plug(app(ui , e), E))) where ui is fresh The CPS counterpart of an expression e is lam(k, Ck (e)), where k is fresh.  The CPS transformer contains five calls to Ck . In three cases, the argument of Ck is the result of plug, and in two cases, the argument is e. As in Section 2.1.1, and since e = plug(e, [ ]), we can say that decompose (in the definition of Ck ) is always called with the result of plug. Let us re-express the CPS transformer with a refocusing function that combines the effects of decompose and plug. 2.2.2 The refocused specification We change the CPS transformer to use refocus instead of decompose and plug. Ck : Exp → Exp Ck (e) = Ck′ (refocus(e, [ ])) Ck′ : Val + (EvCont × PotRedex) Ck′ (v) Ck′ (E, app(x, v)) ′ Ck (E, app(lam(x, e), v)) → = = = Exp app(k, Φ(v)) app(app(x, Kk (E)), Φ(v)) app(lam(x, Ck′ (refocus(e, E))), Φ(v)) Φ : Val → Exp Φ(x) = x Φ(lam(x, M )) = lam(k, lam(x, Ck (e))) where k is fresh Kk : EvCont Kk ([ ]) Kk (E[app(x, [ ])]) Kk (E[app(lam(x, e), [ ])]) Kk (E[app([ ], e)]) → = = = = Exp k app(x, Kk (E)) lam(x, Ck′ (refocus(e, E))) lam(ui , Ck′ (refocus(app(ui , e), E))) where ui is fresh The CPS transformation of an expression e is lam(k, Ck (e)), where k is fresh. 7 We are now free to use any implementation of refocus that is extensionally equivalent to decompose ◦ plug. 2.3 Refocusing efficiently Let us fuse plug and decompose into one function. First, here is their definition. plug(e, [ ]) = e plug(e, E[app([ ], e2 )]) = plug(app(e, e2 ), E) plug(e, E[app(v1 , [ ])]) = plug(app(v1 , e), E) decompose(e) = decompose ′ (e, [ ]) decompose ′ : Exp × EvCont → Val + (EvCont × PotRedex) decompose ′ (v, E) = decompose ′aux (E, v) decompose ′ (app(e1 , e2 ), E) = decompose ′ (e1 , E[app([ ], e2 )]) decompose ′aux : EvCont × Val decompose ′aux ([ ], v) decompose ′aux (E[app([ ], e2 )], v) decompose ′aux (E[app(v1 , [ ])], v) → = = = Val + (EvCont × PotRedex) v decompose ′ (e2 , E[app(v, [ ])]) (E, app(v1 , v)) The definition of plug is a transliteration of the one in Section 1.3. As for decompose, it implements a depth-first search for the first application of one value to another, and uses two auxiliary functions: one, decompose ′ , recursively descends into an expression and accumulates the corresponding context, and the other, decompose ′aux , dispatches on the accumulated context. Let us now construct a more efficient version of refocus = decompose ◦ plug. Our key observation is a consequence of the unique-decomposition property of a syntactic theory of the λ-calculus and of the fact that the grammar of evaluation contexts is context-free: decompose(plug(e, E)) = decompose ′ (e, E) In words: E uniquely determines the partial decomposition of the result of plug(e, E) into the expression e and the evaluation context E. (In general, this decomposition is partial because e is not necessarily a potential redex.) A similar consequence holds for values: decompose(plug(v, E)) = decompose ′aux (E, v) A simple fold-unfold calculation3 lets us calculate the following version of refocus. 3 For example, refocus (app(e1 , e2 ), E) = = = = = decompose (plug (app(e1 , e2 ), E)) decompose ′ (app(e1 , e2 ), E) decompose ′ (e1 , E[app([ ], e2 )]) decompose (plug (e1 , E[app([ ], e2 )])) refocus (e1 , E[app([ ], e2 )]) 8 refocus : Exp × EvCont → Val + (EvCont × PotRedex) refocus(v, E) = refocus aux (E, v) refocus(app(e1 , e2 ), E) = refocus(e1 , E[app([ ], e2 )]) refocus aux : EvCont × Val refocus aux ([ ], v) refocus aux (E[app([ ], e2 )], v) refocus aux (E[app(v1 , [ ])], v) → = = = Val + (EvCont × PotRedex) v refocus(e2 , E[app(v, [ ])]) (E, app(v1 , v)) The resulting function refocus is extensionally equivalent to decompose ◦ plug because fold-unfold transformations are correct [1]. It is also more efficient because it avoids building an intermediate expression for the sole purpose of decomposing it straight away.4 2.3.1 The interpreter The refocused interpreter produces the same result as the original one, but it operates more efficiently. For example (cf. Example 1 in Section 1.3), when evaluating a term such as ⌈n⌉ (λx.x) v, the time taken to refocus from a contractum and a context to a new context and a redex is independent of the number of remaining applications. 2.3.2 The CPS transformer The refocused CPS transformer produces the same compact terms as the original one, but it operates in linear time. More precisely, it operates in one pass over its input.5 2.4 Summary We have described how to refocus a syntactic theory for the call-by-value λ-calculus, by deforesting the intermediate expressions produced in each decomposecontract-plug cycle. As a byproduct, we have shown how refocusing makes it possible to derive a linear-time program transformation out of a quadratic-time one. 4 Replacing the composition of two functions that produce and consume an intermediate data structure is an instance of deforestation [14]. 5 Actually, the transformation could have been optimized to read as follows: Kk (E[app([ ], e)]) = lam(ui , Ck (refocus (e, E[app(ui , [ ])]))) This way, each ui would not be inspected as an expression. 9 3 Refocusing in a syntactic theory In this section we show how to construct a refocus function for a syntactic theory, as an alternative to plugging and decomposing. We prove that this function computes the correct result and we give an exact measure of its computational complexity. Finally we show that refocusing is always at least as efficient as plugging and then decomposing. 3.1 The language A programming language is defined by its syntax and it semantics. Here, its semantics is specified by a syntactic theory. Without loss of generality we assume that its abstract syntax is specified by a context-free grammar (or BNF) with only productions of the form e ::= c(e1 , . . . , en ) where c ranges over a set of constructor names and e, e1 , . . . , en are nonterminals corresponding to the syntactic categories of the language. Each constructor may occur only once in a grammar. 3.2 Syntactic theories A syntactic theory is a specification of a small-steps operational semantics, i.e., it specifies a reduction relation between programs. Syntactic theories traditionally come with context-free grammars for values and evaluation contexts, along with reduction rules of the form E[r] → e, where the term r is called a redex (short for reducible expression). Let us review each of these concepts in turn. 3.2.1 Values The grammar of values extends the BNF of the language with new non-terminals, v’s, one for each non-terminal e. The set of values ranged over by a v is included in the set of terms ranged over by the matching e. The productions for values are all of the form v ::= c(x1 , . . . , xn ) where e ::= c(e1 , . . . , en ) is a production of the language grammar and each xi is either ei or vi . If a syntactic category of the language contains only values, then it is irrelevant whether we use vi or ei in place of xi . From here on we will consistently use a ei for such a syntactic category. 3.2.2 Evaluation contexts Evaluation contexts are also specified by context-free grammars, and their meaning is given by the associated plug function. For example, the evaluation contexts of the call-by-value λ-calculus and the associated plug function are shown in Section 2. 10 All the evaluation contexts of a syntactic theory can be written as the composition of elementary evaluation contexts. Composition is a binary function on evaluation contexts, ◦, that together with the plug function satisfies (E1 ◦ E2 )[e] = E1 [E2 [e]], and is thus associative. Elementary contexts can themselves not be written as compositions of other non-empty contexts.6 For the call-by-value λ-calculus, the elementary contexts are app([ ], e) and app(v, [ ]). The construction of evaluation contexts corresponds to composing an elementary context to the right of another context, e.g., E[app([ ], e)] = E ◦ app([ ], e). In many articles on syntactic theories, though, compound contexts are constructed by composing an elementary context on the left rather than on the right. For the λ-calculus, the grammar of evaluation contexts then reads as follows. E ::= [ ] | app(E, e) | app(v, E) This notation induces the same elementary contexts. 3.2.3 Decompositions and potential redexes If a term, e, is the result of plugging term e ′ into an evaluation context E, i.e., E[e ′ ] = e, then we say that E and e form a decomposition (into an evaluation context and a term) of e ′ , or alternatively that e ′ can be decomposed into E and e. A term can be decomposed in many different ways. If e = E[e ′ ] then the decomposition is trivial if either E is the empty context or e ′ is a value. Values are normal forms: they cannot have a non-value sub-term in a position where it can be evaluated, so all decompositions must be trivial.7 Non-value terms that can only be decomposed trivially are called potential redexes. For the λ-calculus, the potential redexes are exactly the terms of the form app(v, v). If e = E[e ′ ] then the decomposition is complete if e ′ is a potential redex. The reduction rules of a syntactic theory contain only complete decompositions. If a term has a decomposition, E[e], that is not complete, then e can be further decomposed in a non-trivial way as E ′ [e ′ ]. Then (E ◦ E ′ )[e ′ ] is again a non-trivial decomposition of the original term. The context of a complete decomposition is maximal in the sense that further decompositions would be trivial. 3.2.4 Unique decomposition Unique decomposition is a property of syntactic theories that guarantees the existence and uniqueness of complete decompositions for arbitrary terms. Definition 2 (Unique decomposition) A syntactic theory is said to satisfy the unique-decomposition property if any non-value term e can be uniquely decomposed as e = E[r] where r is a potential redex. 6 We note that evaluation contexts, together with composition and the empty context, form a monoid. 7 This does not preclude, e.g., weak head normal forms or lazy pairs. 11 Unique decomposition is so fundamental to syntactic theories for deterministic languages that it is almost always the first property to be established. Its proof is often technically simple, but because of its many small cases, it tends to be tedious and error-prone. This state of affairs motivated Xiao, Sabry, and Ariola to develop an automated support for proving unique-decomposition properties [18].8 3.2.5 Reduction rules The reduction rules of a syntactic theory are of the form E[r] → e where r is a potential redex and e depends only on E and on the sub-terms of r. Often e is written as E[e ′ ] for some e ′ depending on r. The potential redexes that occur in a reduction rule are the actual redexes of the syntactic theory. The remaining potential redexes stick, to use Plotkin’s word [11]. Stuck terms encompass type-incorrect terms (e.g., the application of a literal) and undefined terms (e.g., the application of an identifier). The only reduction rule of the call-by-value λ-calculus is E[app(lam(x, e), x)] → e [v/x], and thus app(lam(x, e), x) is a redex. The remaining potential redexes (of the form app(x, v)) are stuck terms. 3.3 Requirements We state three requirements that are sufficient for constructing a refocus function. We consider syntactic theories whose structure is as described in Section 3.2 and that satisfy unique decomposition. 3.3.1 No redundant constructs Syntactic theories specify a reduction relation. Any part of a specification that does not affect the specified relation is redundant and can be ignored. Specifically: • If the language has a grammar production e ::= c(e1 , . . . , en ) and the syntactic category ranged over by ei contains only values, then an elementary evaluation context of the form c(x1 , . . . , xi−1 , [ ], xi+1 , . . . , xn ) is redundant. Since only values can be plugged in the hole, and values can only be trivially decomposed, then no evaluation context constructed by composing this elementary context can ever be part of a complete decomposition. • If a syntactic category, ranged over by e, contains no values, i.e., the corresponding values ranged over by v is the empty set, then any occurrence of v in a rule makes this rule redundant. (Syntactic categories with no values are not useless; they can occur meaningfully in, e.g., languages with control effects.) 8 Xiao, Sabry, and Ariola also point out that proving unique decomposition reduces to proving equivalence and unambiguity of context-free grammars—two undecidable properties. In SL, they reduce the problem to regular tree languages, for which equivalence and unambiguity are decidable. Similarly, the grammars we consider here are regular tree grammars. 12 • If the syntactic category ranged over by e is empty, then that entire syntactic category and all other productions containing e are redundant and can safely be ignored. The above properties are all decidable. Deciding whether the set of terms ranged over by a non-terminal is empty is decidable for context-free grammars. The specific structure of the productions of values guarantees that values must be a subset of terms, and that it is decidable whether e and v generate the same set of terms, i.e., whether e only ranges over values. 3.3.2 Distinct value constructors and potential-redex constructors By definition, potential redexes are distinct from values, and values are defined by a context-free grammar of the form shown in Section 3.2.1. If an instance of a syntactic construct can become a value by evaluating its sub-terms, then another instance must not also be able to become a redex, and vice-versa. The only way a syntactic construct can become both a value and a potential redex is if the value is given by a production of the form v ::= c(. . . , vi , . . .) and there is no elementary context of the form c(. . . , [ ], . . .) with a hole in the i’th position. Then c(. . . , ei , . . .) is a potential redex when ei is not a value. There are several ways to achieve the property that no syntactic construct can become both a value and a potential redex. We use the equivalent requirement that the potential redexes can be specified by a context-free grammar with productions of the same type as for values. In other words, the productions are of the form r ::= c(x1 , . . . , xn ) where xi is either vi or ei . 3.3.3 Left-to-right evaluation of sub-terms Without loss of generality, we assume that sub-terms of a syntactic construct are ordered so that evaluating them proceeds from left to right. In other words, the unique decomposition into evaluation context and potential redex will always have the hole of the context occurring in the left-most non-evaluated sub-term, in the ordering of sub-terms chosen for the abstract syntax. 3.4 Consequences of the requirements The requirements of Section 3.3 guarantee that a syntactic theory contains elementary evaluation contexts, values, and potential redexes of a specific form. This form is described in Figure 1. An example where the m in Figure 1 is strictly smaller than n is a conditional expression such as if(e, e, e). The only elementary evaluation context is if([ ], e, e), since the other two sub-terms should not be evaluated until the conditional expression has itself been reduced. Another example is the call-by-name λ-calculus where only the function part of an application is evaluated. Lemma 1 A syntactic theory satisfying the requirements stated in Section 3.3 contains productions corresponding to Figure 1. 13 For a syntactic construct with n sub-terms, e ::= c(e1 , . . . , en ), there is a number 0 ≤ m ≤ n such that the elementary evaluation contexts for c are exactly: c([ ], e2 , . . . , en ) c(v1 , [ ], e3 , . . . , en ) .. . c(v1 , . . . , vm−1 , [ ], em+1 , . . . , en ) If terms of the form c(v1 , . . . , vm , em+1 , . . . , en ) exist (i.e., the syntactic category ranged over by em contains values) then they are either all values or all potential redexes. In other words, either v ::= c(v1 , . . . , vm , em+1 , . . . , en ) or r ::= c(v1 , . . . , vm , em+1 , . . . , en ) is a production, but not both. We then say that c needs to evaluate m sub-terms. If c(v1 , . . . , vm , em+1 , . . . , en ) is a value we say that c becomes a value. If it is a potential redex we say that c becomes a potential redex. Figure 1: Elementary contexts, values, and potential redexes Proof (sketch): Take the production e ::= c(e1 , . . . , en ). If there is a value production, v ::= c(e1 , . . . , en ) or potential-redex production r ::= c(e1 , . . . , en ), then m = 0 and c becomes a value or a potential redex. If there is no such production then since e is not redundant, there must exist a term, c(e1 , . . . , en ), that has a non-trivial decomposition and thus there must exist an elementary evaluation context c(e1 , . . . , ei−1 , [ ], ei+1 , . . . , en ). We assume that evaluation proceeds from left to right, so the hole must be in the left-most term, i.e., the elementary context is c([ ], e2 , . . . , en ). Assume that there exists an elementary evaluation context of the form c(v1 , . . . , vi−1 , [ ], ei+1 , . . . , en ). Three cases occur: 1. If ei only ranges over non-values then m = i and c becomes neither a value nor a potential redex. 2. If ei ranges over values and there exists a production v ::= c(v1 , . . . , vi , ei+1 , . . . , en ) or r ::= c(v1 , . . . , vi , ei+1 , . . . , en ) then m = i and c becomes either a value or a potential redex. 14 3. If ei ranges over values, but terms of the form c(v1 , . . . , vi , ei+1 , . . . , en ) are not values or potential redexes, then they must have non-trivial decompositions, so there is a corresponding elementary evaluation context. Since we assume left-to-right evaluation, the context must be c(v1 , . . . , vi , [ ], ei+2 , . . . , en ). In other words, either (1) c(v1 , . . . , vi , ei+1 , . . . , en ) does not exist, or it is a value or a potential redex, and then m = i, or (2) there exists an elementary evaluation context c(v1 , . . . , vi , [ ], ei+2 , . . . , en ), and then m > i. An induction argument shows that m is the smallest i such that (1) holds. There exists such a smallest i: a term of the form c(v1 , . . . , vn ) either does not exist or it has only trivial decompositions, i.e., is either a value or a potential redex, and thus m ≤ n as required.  We have shown that a syntactic theory satisfying the requirements contain the productions of Figure 1. We do not show that there are no further productions. However, we show in the next section that one can compute the unique decomposition of any term using only the productions of Figure 1, and thus any further productions would either be redundant or violate the uniquedecomposition property. 3.5 Constructing a refocus function We construct a function, refocus, that is extensionally equivalent to the composition of the plug function and the decompose function. It uses a stack of elementary contexts to represent evaluation contexts. Such a stack directly corresponds to the representation of contexts of Section 2, and it allows an efficient implementation of composing an elementary evaluation context and plugging a term in a context. We use [ ] for the empty context/stack and E ◦ c(v1 , . . . , vi−1 , [ ], ei+1 , . . . , en ) for composing/pushing an elementary context. The refocusing function is defined with two mutually recursive functions. The first, refocus, takes a stack of elementary evaluation contexts and a term, and is defined by cases of the term. The other function, refocus aux , takes a stack of elementary contexts and a value as arguments, and is defined by cases on the top-most elementary evaluation context on the stack. For each e ::= c(e1 , . . . , en ) in the language grammar, there exists one corresponding rule in refocus. If c needs to evaluate m sub-terms then there are m rules in refocus aux corresponding to the elementary contexts c([ ], e2 , . . . , en ) through c(v1 , . . . , vm−1 , [ ], em+1 , . . . , en ). If refocus and refocus aux are implemented in a typed setting, there is one refocus and one refocus aux for each syntactic category of the language. The rules of refocus mentioned above go in the functions corresponding to the syntactic category ranged over by e. The rules of refocus aux go in the functions corresponding to the syntactic category of the hole, which is the same as the syntactic category of the value argument. 1. If c needs to evaluate zero sub-terms, then we know that c(e1 , . . . , en ) is a value or a potential redex. 15 (a) If c becomes a value, then refocus(c(e1 , . . . , en ), E) = refocus aux (E, c(e1 , . . . , en )) (b) If instead c becomes a potential redex, then refocus(c(e1 , . . . , en ), E) = (E, c(e1 , . . . , en )) since we have found the decomposition. 2. If c needs to evaluate more than zero sub-terms then the first expression must be reduced first, and we simply refocus on it: refocus(c(e1 , . . . , en ), E) = refocus(e1 , E ◦ c([ ], e2 , . . . , en )) Likewise, the refocus aux function is defined by cases on the evaluation context on top of the stack. If c(v1 , . . . , vi−1 , [ ], ei+1 , . . . , en ) is the top-most elementary context on the stack, the corresponding case is one of the following. 1. If c needs to evaluate exactly i sub-terms, then c(v1 , . . . , vi , ei+1 , . . . , en ), if such a term exists, is either a value or a potential redex. (a) If c becomes a value then the case is refocus aux (E ◦ c(v1 , . . . , vi−1 , [ ], ei+1 , . . . , en ), vi ) = refocus aux (E, c(v1 , . . . , vi−1 , vi , ei+1 , . . . , en )) (b) If c becomes a potential redex then we have found a decomposition into evaluation context and potential redex, and the case is refocus aux (E ◦ c(v1 , . . . , vi−1 , [ ], ei+1 , . . . , en ), vi ) = (E, c(v1 , . . . , vi−1 , vi , ei+1 , . . . , en )) (c) If ei ranges over only non-values then there is no rule. In a typed setting there is no refocus aux corresponding to that syntactic category. 2. If c needs to evaluate more than i sub-terms, then we continue searching for a potential redex in the next sub-term, and the rule is: refocus aux (E ◦ c(v1 , . . . , vi−1 , [ ], ei+1 , . . . , en ), vi ) = refocus(ei+1 , E ◦ c(v1 , . . . , vi , [ ], en )) 3. Finally there is one rule for the empty context. refocus aux ([ ], v) = v This base case accounts for the situation where the entire program is a value, so the decomposition has to return a value rather than a a context and a potential redex. 16 In Appendix A, we illustrate the construction of a refocus function for a syntactic theory of arithmetic expressions with precedence. In the following section we show that the refocus function generated by these rules computes a correct decomposition into evaluation context and potential redex, and that it does so at least as efficiently as the combination of plug and decompose. 3.6 Correctness By construction, refocus can only return either a value or a pair of evaluation context and potential redex, since the remaining cases all perform a tail-recursive call. Both refocus and refocus aux are passed a decomposition of a term, i.e., an evaluation context and another term, and if they make a recursive call, it is on another decomposition of the same term. Therefore, if refocus(e, E) returns a decomposition, then it is a complete decomposition of E[e]. It thus suffices to prove that refocus terminates on any argument. Let us show that if E[e] = E ′ [r], where r is a potential redex, then refocus(e, E) performs T (E ′ ) + Taux (r) − T (E) tail-recursive calls, where T and Taux are defined as follows: T : EvCont → N T ([ ]) = 0 Pk T (E ◦ c(v1 , . . . , vk−1 , [ ], ek+1 , . . . , een )) = 1 + T (E) + i=1 (1 + Taux (vi )) Taux : Val + PotRedex → N P m Taux (c(v1 , . . . , vm , em+1 , . . . , en )) = 1 + i=1 (1 + Taux (vi )) Given an evaluation context, T computes the number of edges traversed to go from the root to the hole in a depth-first left-to-right traversal, counting the edges to values both on the way down and on the way up. This traversal is illustrated by the following picture. cO @ ~~ > @@@ ~ @@ ~~ @@ ~ ~~~  v v c ~ ? >>> ~ ~ >> ~~ >>  ~~~  v e c> ~ >> ~ ~ >> ~~ >>  ~~~ e e c> @ >> >> >> >  v e [] As for Taux , it computes the number of edges followed by a full traversal of a composite value. 17 All fully traversed sub-terms must be values. With this definition, the result of T (E) is at most twice the number of nodes visited on such a traversal. We use the T function to measure how far the refocus function has progressed in its traversal of its argument. Lemma 2 (Termination) If E[e] = E ′ [r] then refocus(e, E) terminates after T (E ′ ) + Taux (r) − T (E) tail-recursive calls. Proof: Let us use T to define a progress measure on all the left-hand sides and right-hand sides of the definition of refocus and refocus aux : Progress(refocus(e, E))) Progress(refocus aux (E, v)) Progress((E, r)) Progress(v) = = = = T (E) T (E) + Taux (v) T (E) + Taux (r) Taux (v) With this definition, all choices of arguments to refocus and refocus aux give a right-hand side with a progress value one greater than the left-hand side. We omit the details and give only one case as example. If c needs to evaluate m sub-terms to become a value and 0 < k < m, then refocus aux contains the following case: refocus aux (E ◦ c(v1 , . . . , vk−1 , [ ], ek+1 , ek+2 , . . . , en ), vk ) = refocus(ek+1 , E ◦ c(v1 , . . . , vk−1 , vk , [ ], ek+2 , . . . , en )) Taking the progress measure on both the left-hand side and the right-hand side gives the expected one-greater value on the right-hand side: Progress(refocus aux (E ◦ c(v1 , . . . , vk−1 , [ ], ek+1 , . . . , en ), vi )) = T (E ◦ c(v1 , . . . , vk−1 , [ ], ek+1 , . . . , en )) + Taux (vk ) P = 1 + T (E) + k−1 i=1 (1 + Taux (vi )) + Taux (vk ) and Progress(refocus(ek+1 , E ◦ c(v1 , . . . , vk , [ ], ek+2 , . . . , en ))) = T (E ◦ c(v1 , . . . , vk , [ ], ek+2 , . . . , en )) Pk = 1 + T (E) + i=1 (1 + Taux (vi )) Pk−1 = 1 + T (E) + i=1 (1 + Taux (vi )) + Taux (vk ) + 1 Since each recursive call increases the progress measure by one, and there is only a finite possible number of different such calls (each call is to one of two functions on a decomposition of the same term, and a term has only finitely many decompositions), the recursion must eventually end. When this happens, the cumulative increase in the progress measure is also the number of recursive calls performed to reach it. This number is exactly Progress((E ′ , r)) − Progress(refocus(e, E)) = T (E ′ ) + Taux (r) − T (E).  Lemma 2 tells us both that refocus is a total function and how many calls it takes to find the result. In Section 3.7, we use it to show that refocus is always at least as efficient as using plug and decompose. 18 3.7 Complexity In order to show that refocus is more efficient than the composition of plug and decompose, we must first decide on a relevant complexity measure, and we must find the complexity of plug and decompose. For measuring the time complexity of refocus we use the number of recursive calls as in Section 3.6. Each call performs a bounded number of basic operations on the syntax (either constructing a term or deconstructing a term while naming the sub-terms—the latter is performed implicitly since the function is defined by cases). If each such operation takes constant time, the time taken to compute the result of refocus is proportional to the number of recursive calls (i.e., bounded by a constant factor times the number of calls). Let us informally argue that any implementation of a function decompose : Exp → Val + (EvCont × PotRedex) finding the unique complete decomposition of terms (in a syntactic category satisfying our requirements) must perform at least (T (E) + Taux (r))/2 basic operations on the syntax to compute decompose(E[r]) = (E, r). If it uses fewer operations, it cannot inspect all the values of the evaluation context and of the potential redex. If we changed such a value to a non-value, then the hole of the evaluation context should be in the new non-value term, but the function cannot see this without inspecting the value and thus it must generate an erroneous result, contradicting the assumption that it computes the unique complete decomposition. The plug function is implemented in time proportional to the depth of the hole in the evaluation context. Since the time complexity of decompose is always greater, we ignore plug in our comparison. In summary, the complexity of computing decompose(E[e]) = E ′ [r] is at least proportional to T (E ′ ) + Taux (r), whereas the complexity of computing refocus(e, E) = E ′ [r] is proportional to T (E ′ ) + Taux (r) − T (E). For syntactic theories where rewrites are often local, the difference between T (E) and T (E ′ ) is often significantly smaller than the value of T (E ′ ) itself. For example, in Example 1 of Section 1.3 the difference between T (E) and T (E ′ ) is constant whereas T (E ′ ) alone is proportional to the size of the program. In some cases, refocus does no better than decompose, e.g., when the context argument to refocus is always empty (e.g., for tail-recursive or continuationpassing style λ-terms, and for trampoline-like programs with control operators where the context is discarded in each step). Likewise, if the size of the context is bounded, the saving is at most a constant factor. 3.8 Summary We have presented a few mild requirements to construct a refocus function for a syntactic theory. We have also proven the correctness of this construction and the complexity of the constructed function. Refocusing is at least as efficient as first plugging and then decomposing, and it is often significantly more efficient. 19 4 Conclusion and issues We have presented a general result about syntactic theories with context-free grammars of values, evaluation contexts, and redexes, and satisfying a uniquedecomposition property. This result enables one to mechanically derive an interpreter that does not incur a linear-time overhead for each reduction step. We have illustrated the result with two interpreters, one for the call-by-value λ-calculus and one for arithmetic expressions with precedence, and by mechanically turning a quadratic-time program transformation into a program transformation that operates in one pass. Acknowledgments: We are grateful to Daniel Damian, Bernd Grobauer, Fritz Henglein, and Julia Lawall for commenting a previous version of this article. Thanks are also due to the anonymous RULE’01 referees. This work is supported by the ESPRIT Working Group APPSEM (http: //www.md.chalmers.se/Cs/Research/Semantics/APPSEM/). A Arithmetic expressions with precedence This section treats a comprehensive example illustrating all the cases of the construction of a refocus function as presented in Section 3.5. We consider arithmetic expressions with additions, multiplications, conditional expressions checking whether their first argument is zero, parenthesized expressions, literals, and oracles returning 0 or 1. (The oracles are only there to illustrate a case of the construction.) The grammar is unambiguous and hierarchic, as often given in compiler courses. It specifies expressions, terms, and factors, and reads as follows. e∈ t∈ f∈ n∈ Expr Term Fact Lit e ::= t + e | Ifz e e e | t t ::= f × t | f f ::= n | flip | (e) A program is an expression. A.1 A syntactic theory We define the syntactic theory of arithmetic expressions by specifying its values, its computations, its evaluation contexts, its redexes and its reduction rules. Values (trivial terms): ve ∈ ExprVal vt ∈ TermVal vf ∈ FactVal 20 ve ::= vt vt ::= vf vf ::= n Computations (serious terms): ce ∈ ExprComp ct ∈ TermComp cf ∈ FactComp ce ::= t + e | Ifz e e e | ct ct ::= f × t | cf cf ::= flip | (e) Evaluation contexts: Ee ∈ ExprEvCont Et ∈ TermEvCont Ef ∈ FactEvCont Ee ::= [ ]e | Et + e | vt + Ee | Ifz Ee e e | Et Et ::= [ ]t | Ef × t | vf × Et | Ef Ef ::= [ ]f | (Ee ) Redexes: re ∈ ExprRedex rt ∈ TermRedex rf ∈ FactRedex re ::= vt + ve | Ifz ve e e | rt rt ::= vf × vt | rf rf ::= flip | (ve ) Reduction rules: Ee [n1 + n2 ] Ee [Ifz n e1 e2 ] Ee [Ifz n e1 e2 ] Ee [n1 × n2 ] Ee [flip] Ee [flip] Ee [(n)] → → → → → → → Ee [n3 ] Ee [e1 ] Ee [e2 ] Ee [n3 ] Ee [0] Ee [1] Ee [n] where n3 is the sum of n1 and n2 if n = 0 if n 6= 0 where n3 is the product of n1 and n2 The reduction rules are defined only on decompositions that “make sense”, i.e., only an expression is plugged into [ ]e , only a term into [ ]t and a factor into [ ]f . The result of a reduction is an expression. This syntactic theory satisfies three unique-decomposition lemmas, i.e., decomposing an expression (resp. a term and a factor) into an evaluation context and a redex yields a unique result. We prove these lemmas by structural induction on the syntax. The language is simple enough that the number of cases is manageable. There are no stuck terms, and reduction always yields an expression. The reduction relation, however, is not a function from expressions to expressions, even though decompositions are unique, because of the oracles. 21 A.2 Evaluation contexts with right-composition In Section A.1, the grammar of evaluation contexts embodies left composition, i.e., the construction of contexts by composition with an elementary context on the left. In other words, each production except for the empty context, e.g., Ee ::= vt + Ee , can be written as Ee ::=(vt + [ ]e ) ◦ Ee . Since composition of contexts is associative, we can define the same contexts using right composition, giving a production of the form Ee ::= Ee ◦(vt + [ ]e ). We write it, however, in the more common way: Ee ::= Ee [vt + [ ]e ]. We transform the grammar of contexts into one representing composition on the right, and we do this in a completely mechanical way. The example above shows the general idea, though it does not illustrate the case where the sub-context is not of the same type as the generated context. We have three groups of evaluation contexts, grouped by the syntactic category they produce when plugged. Take the elementary context [ ]t + e. If we left-compose another evaluation context with this elementary context, we require that the other context produces terms. If we right-compose this elementary context, however, we require the other context to accept expressions in the hole. To capture this restriction in the grammar, we group the productions by the type of the hole instead. So for example, the production Ee ::= Et + e is transformed into Et ::= Ee [[ ]t + e]. In general, the transformation rewrites a production of the form Ea ::= c(x1 , . . . , Eb , . . . , xn ) into Eb ::= Ea [c(x1 , . . . , [ ]b , . . . , xn )], and it keeps the productions of empty contexts. Performing this transformation on the grammar of evaluation contexts above gives the following grammar. Evaluation contexts: Ee ∈ ExprEvCont Et ∈ TermEvCont Ef ∈ FactEvCont Ee ::= [ ]e | Ee [vt + [ ]e ] | Ee [Ifz [ ]e e e] | Ef [([ ])] Et ::= [ ]t | Ee [[ ]t + e] | Et [vf × [ ]t ] | Ee Ef ::= [ ]f | Et [[ ]f × t] | Et In the original representation, Ee ranged over all contexts that generated expression, but made no restriction on the type of the hole. The second representation likewise lets Ee range over all contexts with a hole accepting an expression, but makes no restriction on the type of the result of a plug. Using this second representation, we must require that the evaluation contexts appearing in the reduction rules must output expressions, i.e., we rule out the empty contexts from Et and Ef in the grammar of evaluation contexts. The resulting evaluation contexts and reduction rules read as follows. Evaluation contexts: Ee ∈ ExprEvCont Et ∈ TermEvCont Ef ∈ FactEvCont Ee ::= [ ]e | Ee [vt + [ ]e ] | Ee [Ifz [ ]e e e] | Ef [([ ])] Et ::= Ee [[ ]t + e] | Et [vf × [ ]t ] | Ee Ef ::= Et [[ ]f × t] | Et 22 Reduction rules: Ee [n1 + n2 ] Ee [Ifz n e1 e2 ] Ee [Ifz n e1 e2 ] Et [n1 × n2 ] Ef [flip] Ef [flip] Ef [(n)] datatype expr = | and expr_val = and expr_comp = | | and term = | and term_val = and term_comp = | and fact = | and fact_val = and fact_comp = | → → → → → → → Ee [n3 ] Ee [e1 ] Ee [e2 ] Et [n3 ] Ef [0] Ef [1] Ef [n] where n3 is the sum of n1 and n2 if n = 0 if n 6= 0 where n3 is the product of n1 and n2 EXPR_VAL of expr_val EXPR_COMP of expr_comp TERM_VAL’ of term_val ADD of term * expr IFZ of expr * expr * expr TERM_COMP’ of term_comp TERM_VAL of term_val TERM_COMP of term_comp FACT_VAL’ of fact_val MUL of fact * term FACT_COMP’ of fact_comp FACT_VAL of fact_val FACT_COMP of fact_comp INT of int FLIP PARENS of expr Figure 2: Abstract syntax of arithmetic expressions A.3 Implementation Figure 2 displays the BNF of arithmetic expressions in Standard ML. As usual with syntactic theories, we distinguish between values (trivial terms) and computations (serious terms). An expression (expr) is thus trivial (expr val) or serious (expr comp); a term (term) is trivial (term val) or serious (term comp); and a factor (fact) is trivial (fact val) or serious (fact comp). The only values are integers, hence values are defined with the hierarchy of types expr val, term val, fact val, and int. Computations are similarly embedded, hence the hierarchy of types expr comp, term comp, and fact comp. Figure 3 displays the evaluation contexts and Figure 4, the corresponding plug functions. Figure 5 displays the implementation of redexes and the result of decomposition, and Figure 6, the corresponding decomposition functions. 23 datatype expr_evcont = | | | and term_evcont = | | and fact_evcont = | EEC0 EEC1 EEC2 EEC3 TEC1 TEC2 TEC3 FEC1 FEC2 of of of of of of of of term_val * expr_evcont expr * expr * expr_evcont fact_evcont expr * expr_evcont fact_val * term_evcont expr_evcont term * term_evcont term_evcont Figure 3: Evaluation contexts for arithmetic expressions (* plug_expr : expr * expr_evcont -> expr *) (* plug_term : term * term_evcont -> expr *) (* plug_fact : fact * fact_evcont -> expr *) fun plug_expr (e, EEC0) = e | plug_expr (e, EEC1 (tt, eec)) = plug_expr (EXPR_COMP (ADD (TERM_VAL tt, e)), eec) | plug_expr (e, EEC2 (e1, e2, eec)) = plug_expr (EXPR_COMP (IFZ (e, e1, e2)), eec) | plug_expr (e, EEC3 fec) = plug_fact (FACT_COMP (PARENS e), fec) and plug_term (t, TEC1 (e, eec)) = plug_expr (EXPR_COMP (ADD (t, e)), eec) | plug_term (t, TEC2 (tf, tec)) = plug_term (TERM_COMP (MUL (FACT_VAL tf, t)), tec) | plug_term (TERM_VAL tt, TEC3 eec) = plug_expr (EXPR_VAL (TERM_VAL’ tt), eec) | plug_term (TERM_COMP st, TEC3 eec) = plug_expr (EXPR_COMP (TERM_COMP’ st), eec) and plug_fact (f, FEC1 (t, tec)) = plug_term (TERM_COMP (MUL (f, t)), tec) | plug_fact (FACT_VAL tf, FEC2 tec) = plug_term (TERM_VAL (FACT_VAL’ tf), tec) | plug_fact (FACT_COMP sf, FEC2 tec) = plug_term (TERM_COMP (FACT_COMP’ sf), tec) Figure 4: Plug functions for arithmetic expressions 24 datatype expr_redex = ADD_REDEX of term_val * expr_val | IFZ_REDEX of expr_val * expr * expr datatype term_redex = MUL_REDEX of fact_val * term_val datatype fact_redex = FLIP_REDEX | PARENS_REDEX of expr_val datatype decomposed = | | | VALUE of expr_val EXPR_DECOMPOSITION of expr_evcont * expr_redex TERM_DECOMPOSITION of term_evcont * term_redex FACT_DECOMPOSITION of fact_evcont * fact_redex Figure 5: Redexes and the result of decomposition for arithmetic expressions 25 (* (* (* (* fun decompose_expr decompose_expr_comp decompose_term_comp decompose_fact_comp : : : : expr -> expr_comp * expr_evcont -> term_comp * term_evcont -> fact_comp * fact_evcont -> decomposed decomposed decomposed decomposed decompose_expr (EXPR_VAL te) = VALUE te | decompose_expr (EXPR_COMP se) = decompose_expr_comp (se, EEC0) and decompose_expr_comp (ADD (TERM_VAL tt, EXPR_VAL te), eec) = EXPR_DECOMPOSITION (eec, ADD_REDEX (tt, te)) | decompose_expr_comp (ADD (TERM_VAL tt, EXPR_COMP se), eec) = decompose_expr_comp (se, EEC1 (tt, eec)) | decompose_expr_comp (ADD (TERM_COMP st, e), eec) = decompose_term_comp (st, TEC1 (e, eec)) | decompose_expr_comp (IFZ (EXPR_VAL te, e1, e2), eec) = EXPR_DECOMPOSITION (eec, IFZ_REDEX (te, e1, e2)) | decompose_expr_comp (IFZ (EXPR_COMP se, e1, e2), eec) = decompose_expr_comp (se, EEC2 (e1, e2, eec)) | decompose_expr_comp (TERM_COMP’ st, eec) = decompose_term_comp (st, TEC3 eec) and decompose_term_comp (MUL (FACT_VAL tf, TERM_VAL tt), tec) = TERM_DECOMPOSITION (tec, MUL_REDEX (tf, tt)) | decompose_term_comp (MUL (FACT_VAL tf, TERM_COMP st), tec) = decompose_term_comp (st, TEC2 (tf, tec)) | decompose_term_comp (MUL (FACT_COMP sf, t), tec) = decompose_fact_comp (sf, FEC1 (t, tec)) | decompose_term_comp (FACT_COMP’ sf, tec) = decompose_fact_comp (sf, FEC2 tec) and decompose_fact_comp (FLIP, fec) = FACT_DECOMPOSITION (fec, FLIP_REDEX) | decompose_fact_comp (PARENS (EXPR_VAL te), fec) = FACT_DECOMPOSITION (fec, PARENS_REDEX te) | decompose_fact_comp (PARENS (EXPR_COMP se), fec) = decompose_expr_comp (se, EEC3 fec) Figure 6: Decomposition of an arithmetic expression 26 *) *) *) *) (* eval : expr -> expr_val *) (* eval’ : decomposed -> expr_val *) fun eval e = eval’ (decompose_expr e) and eval’ (VALUE te) = te | eval’ (EXPR_DECOMPOSITION (eec, ADD_REDEX (FACT_VAL’ (INT n1), TERM_VAL’ (FACT_VAL’ (INT n2))))) = eval (plug_expr (EXPR_VAL (TERM_VAL’ (FACT_VAL’ (INT (n1 + n2)))), eec)) | eval’ (EXPR_DECOMPOSITION (eec, IFZ_REDEX (TERM_VAL’ (FACT_VAL’ (INT 0)), e1, e2))) = eval (plug_expr (e1, eec)) | eval’ (EXPR_DECOMPOSITION (eec, IFZ_REDEX (TERM_VAL’ (FACT_VAL’ (INT n)), e1, e2))) = eval (plug_expr (e2, eec)) | eval’ (TERM_DECOMPOSITION (tec, MUL_REDEX (INT n1, FACT_VAL’ (INT n2)))) = eval (plug_term (TERM_VAL (FACT_VAL’ (INT (n1 * n2))), tec)) | eval’ (FACT_DECOMPOSITION (fec, FLIP_REDEX)) = eval (plug_fact (FACT_VAL (INT (Oracle.flip ())), fec)) | eval’ (FACT_DECOMPOSITION (fec, PARENS_REDEX (TERM_VAL’ (FACT_VAL’ (INT n))))) = eval (plug_fact (FACT_VAL (INT n), fec)) Figure 7: Evaluation of an arithmetic expression Figure 7 displays the evaluation function, which attempts to decompose its input expression into an evaluation context and a redex. If the expression is a value (and thus cannot be decomposed), then the result is found. Otherwise, the redex is contracted, the contractum is plugged into the context, and evaluation is iterated. Contracting a redex amounts to adding two integers, selecting between two expressions, multiplying two integers, calling an oracle (which is unspecified here; our implementation is state-based), or skipping parentheses. No terms are stuck. A.4 Refocusing We now construct the refocus functions. We start from the ML definition of the grammar, in Figure 2, which fits the format expected for the construction. Let us determine the reduction sequence of each syntactic construct. 27 The main syntactic constructs: Both addition and multiplication evaluate their arguments from left to right. The conditional expression is special in that it only evaluates its first argument. The oracle has no argument. The parentheses have one argument and evaluate it. All of these syntactic constructs create redexes. The auxiliary syntactic constructs: The coercions between terms and expressions and between factors and expressions have one argument. This argument is evaluated. The refocus functions follow the grammatical structure of the source language. Their skeleton is thus as follows. fun refocus_expr (EXPR_VAL te, eec) = refocus_expr_val (te, eec) | refocus_expr (EXPR_COMP se, eec) = refocus_expr_comp (se, eec) and refocus_expr_val (te, eec) = ... and refocus_expr_comp (ADD (t, e), eec) = ... | refocus_expr_comp (IFZ (e0, e1, e2), eec) = ... | refocus_expr_comp (TERM_COMP’ st, eec) = ... and refocus_term (TERM_VAL tt, tec) = refocus_term_val (tt, tec) | refocus_term (TERM_COMP st, tec) = refocus_term_comp (st, tec) and refocus_term_val (tt, tec) = ... and refocus_term_comp (MUL (f, t), tec) = ... | refocus_term_comp (FACT_COMP’ sf, tec) = ... and refocus_fact (FACT_VAL tf, fec) = refocus_fact_val (tf, fec) | refocus_fact (FACT_COMP sf, fec) = refocus_fact_comp (sf, fec) and refocus_fact_val (tf, fec) = ... and refocus_fact_comp (FLIP, fec) = ... | refocus_fact_comp (PARENS e, fec) = ... In the rest of this section, we complete each missing case in this definition. 1. If a constructor needs to evaluate zero sub-terms, then the result of the production is a value or a redex. 28 (a) The case of values is already taken care of because the grammar differentiates between values and computations. All functions refocus x call refocus x val if given a value, and call refocus x comp if given a computation. (b) If the result of a production is a redex, then we have found a decomposition. Here, only the oracle fits the bill. We thus return a decomposition. refocus_fact_comp (FLIP, fec) = FACT_DECOMP (fec, FLIP_REDEX) 2. If the constructor needs to evaluate more than zero sub-terms then the first sub-expression must be evaluated. refocus_expr_comp (ADD (t, e), eec) = refocus_term (t, TEC1 (e, eec)) | refocus_expr_comp (IFZ (e0, e1, e2), eec) = refocus_expr (e0, EEC2 (e1, e2, eec)) | refocus_expr_comp (TERM_COMP’ st, eec) = refocus_term_comp (st, TEC3 eec) refocus_term_comp (MUL (f, t), tec) = refocus_fact (f, FEC1 (t, tec)) | refocus_term_comp (FACT_COMP’ sf, tec) = refocus_fact_comp (sf, FEC2 tec) | refocus_fact_comp (PARENS e, fec) = refocus_expr (e, EEC3 fec) 3. Likewise, each refocus x val is defined by cases on the inner elementary evaluation context. refocus_expr_val = ... | refocus_expr_val = ... | refocus_expr_val = ... | refocus_expr_val = ... (te, EEC0) (te, EEC1 (tt, eec)) (te, EEC2 (e1, e2, eec)) (te, EEC3 fec) refocus_term_val (tt, TEC1 (e, eec)) = ... | refocus_term_val (tt, TEC2 (tf, tec)) = ... | refocus_term_val (tt, TEC3 eec) = ... 29 refocus_fact_val (tf, FEC1 (t, tec)) = ... | refocus_fact_val (tf, FEC2 tec) = ... (a) If all the sub-terms needed by a constructor are evaluated and the production constructs a value, we refocus on this value. | refocus_term_val (tt, TEC3 eec) = refocus_expr_val (TERM_VAL’ tt, eec) | refocus_fact_val (tf, FEC2 tec) = refocus_term_val (FACT_VAL’ tf, tec) (b) If all the sub-terms needed by a constructor are evaluated and the production constructs a redex, we have found a decomposition. | refocus_expr_val (te, EEC1 (tt, eec)) = EXPR_DECOMP (eec, ADD_REDEX (tt, te)) | refocus_expr_val (te, EEC2 (e1, e2, eec)) = EXPR_DECOMP (eec, IFZ_REDEX (te, e1, e2)) | refocus_expr_val (te, EEC3 fec) = FACT_DECOMP (fec, PARENS_REDEX te) | refocus_term_val (tt, TEC2 (tf, tec)) = TERM_DECOMP (tec, MUL_REDEX (tf, tt)) (c) If the constructor needs to evaluate more sub-terms, we refocus on the next sub-expression to be reduced. refocus_term_val (tt, TEC1 (e, eec)) = refocus_expr (e, EEC1 (tt, eec)) refocus_fact_val (tf, FEC1 (t, tec)) = refocus_term (t, TEC2 (tf, tec)) 4. Finally, we turn to the empty context: refocus_expr_val (te, EEC0) = VALUE te The refocus functions are now complete (see Figure 8). The main evaluation function is displayed in Figure 9. Rather than decomposing the result of the plug functions, eval’ refocuses from a contractum to the next decomposition and iterates. 30 fun refocus_expr (EXPR_VAL te, eec) = refocus_expr_val (te, eec) | refocus_expr (EXPR_COMP se, eec) = refocus_expr_comp (se, eec) and refocus_expr_val (te, EEC0) = VALUE te | refocus_expr_val (te, EEC1 (tt, eec)) = EXPR_DECOMPOSITION (eec, ADD_REDEX (tt, te)) | refocus_expr_val (te, EEC2 (e1, e2, eec)) = EXPR_DECOMPOSITION (eec, IFZ_REDEX (te, e1, e2)) | refocus_expr_val (te, EEC3 fec) = FACT_DECOMPOSITION (fec, PARENS_REDEX te) and refocus_expr_comp (ADD (t, e), eec) = refocus_term (t, TEC1 (e, eec)) | refocus_expr_comp (IFZ (e0, e1, e2), eec) = refocus_expr (e0, EEC2 (e1, e2, eec)) | refocus_expr_comp (TERM_COMP’ st, eec) = refocus_term_comp (st, TEC3 eec) and refocus_term (TERM_VAL tt, tec) = refocus_term_val (tt, tec) | refocus_term (TERM_COMP st, tec) = refocus_term_comp (st, tec) and refocus_term_val (tt, TEC1 (e, eec)) = refocus_expr (e, EEC1 (tt, eec)) | refocus_term_val (tt, TEC2 (tf, tec)) = TERM_DECOMPOSITION (tec, MUL_REDEX (tf, tt)) | refocus_term_val (tt, TEC3 eec) = refocus_expr_val (TERM_VAL’ tt, eec) and refocus_term_comp (MUL (f, t), tec) = refocus_fact (f, FEC1 (t, tec)) | refocus_term_comp (FACT_COMP’ sf, tec) = refocus_fact_comp (sf, FEC2 tec) and refocus_fact (FACT_VAL tf, fec) = refocus_fact_val (tf, fec) | refocus_fact (FACT_COMP sf, fec) = refocus_fact_comp (sf, fec) and refocus_fact_val (tf, FEC1 (t, tec)) = refocus_term (t, TEC2 (tf, tec)) | refocus_fact_val (tf, FEC2 tec) = refocus_term_val (FACT_VAL’ tf, tec) and refocus_fact_comp (FLIP, fec) = FACT_DECOMPOSITION (fec, FLIP_REDEX) | refocus_fact_comp (PARENS e, fec) = refocus_expr (e, EEC3 fec) Figure 8: Refocusing over an arithmetic expression 31 (* (* (* (* (* (* (* (* (* refocus_expr refocus_expr_val refocus_expr_comp refocus_term refocus_term_val refocus_term_comp refocus_fact refocus_fact_val refocus_fact_comp : : : : : : : : : expr expr_val expr_comp term term_val term_comp fact fact_val fact_comp * * * * * * * * * expr_evcont expr_evcont expr_evcont term_evcont term_evcont term_evcont fact_evcont fact_evcont fact_evcont -> -> -> -> -> -> -> -> -> decomposed decomposed decomposed decomposed decomposed decomposed decomposed decomposed decomposed *) *) *) *) *) *) *) *) *) (* eval : expr -> expr_val *) (* eval’ : decomposed -> expr_val *) fun eval e = eval’ (refocus_expr (e, EEC0)) and eval’ (VALUE te) = te | eval’ (EXPR_DECOMPOSITION (eec, ADD_REDEX (FACT_VAL’ (INT n1), TERM_VAL’ (FACT_VAL’ (INT n2))))) = eval’ (refocus_expr (EXPR_VAL (TERM_VAL’ (FACT_VAL’ (INT (n1 + n2)))), eec)) | eval’ (EXPR_DECOMPOSITION (eec, IFZ_REDEX (TERM_VAL’ (FACT_VAL’ (INT 0)), e1, e2))) = eval’ (refocus_expr (e1, eec)) | eval’ (EXPR_DECOMPOSITION (eec, IFZ_REDEX (TERM_VAL’ (FACT_VAL’ (INT n)), e1, e2))) = eval’ (refocus_expr (e2, eec)) | eval’ (TERM_DECOMPOSITION (tec, MUL_REDEX (INT n1, FACT_VAL’ (INT n2)))) = eval’ (refocus_term (TERM_VAL (FACT_VAL’ (INT (n1 * n2))), tec)) | eval’ (FACT_DECOMPOSITION (fec, FLIP_REDEX)) = eval’ (refocus_fact (FACT_VAL (INT (Oracle.flip ())), fec)) | eval’ (FACT_DECOMPOSITION (fec, PARENS_REDEX (TERM_VAL’ (FACT_VAL’ (INT n))))) = eval’ (refocus_fact (FACT_VAL (INT n), fec)) Figure 9: Evaluation of an arithmetic expression, refocused 32 A.5 Summary We have considered arithmetic expressions with precedence and we have specified their meaning with a syntactic theory. We then applied the algorithm of Section 3.5 to write refocusing functions that map an evaluation context and a contractum into either a value or a decomposition into an evaluation context and a new redex. The result is a compositional evaluator that operates in one pass over its input. References [1] Rod M. Burstall and John Darlington. A transformational system for developing recursive programs. Journal of ACM, 24(1):44–67, 1977. [2] Olivier Danvy and Andrzej Filinski. Representing control, a study of the CPS transformation. Mathematical Structures in Computer Science, 2(4):361–391, 1992. [3] Olivier Danvy and Lasse R. Nielsen. CPS transformation of beta-redexes. In Amr Sabry, editor, Proceedings of the Third ACM SIGPLAN Workshop on Continuations, Technical report 545, Computer Science Department, Indiana University, pages 35–39, London, England, January 2001. Also available as the technical report BRICS RS-00-35. [4] Olivier Danvy and Lasse R. Nielsen. Syntactic theories in practice. In Mark van den Brand and Rakesh M. Verma, editors, Informal proceedings of the Second International Workshop on Rule-Based Programming (RULE 2001), volume 59.4 of Electronic Notes in Theoretical Computer Science, Firenze, Italy, September 2001. Extended version available as the technical report BRICS RS-02-04. [5] Matthias Felleisen. The Calculi of λ-v-CS Conversion: A Syntactic Theory of Control and State in Imperative Higher-Order Programming Languages. PhD thesis, Department of Computer Science, Indiana University, Bloomington, Indiana, August 1987. [6] Matthias Felleisen and Matthew Flatt. Programming languages and lambda calculi. Unpublished lecture notes. http://www.ccs.neu.edu/ home/matthias/3810-w02/readings.html, 1989-2001. [7] Matthias Felleisen and Daniel P. Friedman. Control operators, the SECD machine, and the λ-calculus. In Martin Wirsing, editor, Formal Description of Programming Concepts III, pages 193–217. Elsevier Science Publishers B.V. (North-Holland), Amsterdam, 1986. [8] John Greiner. Semantics-based parallel cost models and their use in provably efficient implementations. PhD thesis, School of Computer Science, Carnegie Mellon University, Pittsburgh, Pennsylvania, April 1997. Technical Report CMU-CS-97-113. 33 [9] Gilles Kahn. Natural semantics. Technical Report 601, INRIA, Sophia Antipolis, France, February 1987. [10] Lasse R. Nielsen. A study of defunctionalization and continuation-passing style. PhD thesis, BRICS PhD School, University of Aarhus, Aarhus, Denmark, July 2001. BRICS DS-01-7. [11] Gordon D. Plotkin. Call-by-name, call-by-value and the λ-calculus. Theoretical Computer Science, 1:125–159, 1975. [12] Amr Sabry and Matthias Felleisen. Reasoning about programs in continuation-passing style. Lisp and Symbolic Computation, 6(3/4):289–360, 1993. [13] Joseph Stoy. Some mathematical aspects of functional programming. In John Darlington, Peter Henderson, and David A. Turner, editors, Functional Programming and its Applications. Cambridge University Press, 1982. [14] Philip Wadler. Deforestation: Transforming programs to eliminate trees. Theoretical Computer Science, 73(2):231–248, 1989. [15] Philip Wadler. The essence of functional programming (invited talk). In Andrew W. Appel, editor, Proceedings of the Nineteenth Annual ACM Symposium on Principles of Programming Languages, pages 1–14, Albuquerque, New Mexico, January 1992. ACM Press. [16] Mitchell Wand. Correctness of procedure representations in higher-order assembly language. In Stephen Brookes, Michael Main, Austin Melton, Michael Mislove, and David Schmidt, editors, Proceedings of the 7th International Conference on Mathematical Foundations of Programming Semantics, number 598 in Lecture Notes in Computer Science, pages 294–311, Pittsburgh, Pennsylvania, March 1991. Springer-Verlag. [17] Yong Xiao, Zena M. Ariola, and Michel Mauny. From syntactic theories to interpreters: A specification language and its compilation. In Nachum Dershowitz and Claude Kirchner, editors, Informal proceedings of the First International Workshop on Rule-Based Programming (RULE 2000), Montréal, Canada, September 2000. Available online at http: //www.loria.fr/~ckirchne/=rule2000/proceedings/. [18] Yong Xiao, Amr Sabry, and Zena M. Ariola. From syntactic theories to interpreters: Automating proofs of decomposition lemma. Higher-Order and Symbolic Computation, 14(4):387–409, 2001. 34 Recent BRICS Report Series Publications RS-02-4 Olivier Danvy and Lasse R. Nielsen. Syntactic Theories in Practice. January 2002. 34 pp. This revised report supersedes the earlier BRICS report RS-01-31. RS-02-3 Olivier Danvy and Lasse R. Nielsen. On One-Pass CPS Transformations. January 2002. 18 pp. RS-02-2 Lasse R. Nielsen. A Simple Correctness Proof of the Direct-Style Transformation. January 2002. RS-02-1 Claus Brabrand, Anders Møller, and Michael I. Schwartzbach. The <bigwig> Project. January 2002. 36 pp. This revised report supersedes the earlier BRICS report RS-00-42. RS-01-55 Daniel Damian and Olivier Danvy. A Simple CPS Transformation of Control-Flow Information. December 2001. RS-01-54 Daniel Damian and Olivier Danvy. Syntactic Accidents in Program Analysis: On the Impact of the CPS Transformation. December 2001. 41 pp. To appear in the Journal of Functional Programming. This report supersedes the earlier BRICS report RS-00-15. RS-01-53 Zoltán Ésik and Masami Ito. Temporal Logic with Cyclic Counting and the Degree of Aperiodicity of Finite Automata. December 2001. 31 pp. RS-01-52 Jens Groth. Extracting Witnesses from Proofs of Knowledge in the Random Oracle Model. December 2001. 23 pp. RS-01-51 Ulrich Kohlenbach. On Weak Markov’s Principle. December 2001. 10 pp. RS-01-50 Jiřı́ Srba. Note on the Tableau Technique for Commutative Transition Systems. December 2001. 19 pp. To appear in Nielsen and Engberg, editors, Foundations of Software Science and Computation Structures, FoSSaCS ’02 Proceedings, LNCS 2303, 2002. RS-01-49 Olivier Danvy and Lasse R. Nielsen. A First-Order One-Pass CPS Transformation. December 2001. 21 pp. Extended version of a paper to appear in Nielsen and Engberg, editors, Foundations of Software Science and Computation Structures, FoSSaCS ’02 Proceedings, LNCS 2303, 2002.