Typed Exceptions and Continuations Cannot Macro-Express Each Other Jon G. Riecke1 and Hayo Thielecke2
2
1 Bell Laboratories, Lucent Technologies 700 Mountain Avenue, Murray Hill, NJ 07974 USA
[email protected] Queen Mary and Westfield College, University of London, London E1 4NS, UK
[email protected]
Abstract. The most powerful control constructs in modern programming languages are continuations and exceptions. Although they can be used interchangeably in some cases, they are fundamentally different semantically. We consider two simply-typed functional languages with exceptions and continuations, respectively. We give theorems, one that holds in the language with exceptions and one that holds in the language with continuations. A fortiori, these theorems imply that exception constructs cannot be used to macro-express continuation constructs, and that continuation constructs cannot be used to macro-express exception constructs.
1 Introduction Nearly all programming languages contain constructs for manipulating the control flow by “jumping”. The most powerful of these control constructs are exceptions as in Standard ML [15], CAML [11], or Java [6] on the one hand, and continuations as in Standard ML of New Jersey or Scheme [8] on the other. Unlike crude goto, they allow jumps into and out of procedures, and they carry values along with the jump. There are two crucial differences between exceptions and continuations. One difference lies in the fact that when an exception is raised, control is transferred to the dynamically enclosing exception handler, whereas continuations are entirely static. The other difference lies in the indefinite extent of continuations: a procedure may invoke a continuation that was created at its point of definition, even if the procedure containing that definition has already returned. Such “upward” continuations have no counterpart with exceptions. On the other hand, the dynamic nature of handlers is also a source of additional expressive power. One consequence of these differences is that exceptions can be stack-allocated, while continuations with their indefinite extent need to be heap-allocated in general. Although illuminating, this does not constitute a firm distinction: one could conceive a (very inefficient) implementation of continuations with a stack that is never popped (compare [23] in light of [20]). Hence we need a more absolute criterion for comparing expressiveness. Two such criteria are pertinent here: Turing completeness, and the ability to break contextual equivalences [5].
Lillibridge [12, 13] compares continuations and exceptions in terms of Turing completeness. His argument relies on one key fact: the languages are simply-typed and have no explicit recursion. First, Lillibridge shows, using the continuation-passing-style (cps) transform, that all expressions in the simply-typed language with continuations terminate. Second, he shows that property does not hold for exceptions. Even though this argument sheds some light on the distinctions between continuations and exceptions, it is only a partial answer, as Lillibridge states [13]. The main problem is that the argument is not robust. For instance, if we add recursion to the core language, or, more aggressively, recursive types, then the language of continuations becomes Turing complete as well. In fact, Lillibridge’s proof relies on the special status of the exn type, and not much on the exception and continuation operations themselves. A notion of expressiveness more generally applicable than Turing completeness was used by Landin [5, 9, 10]: It is impossible to introduce assignment into pure L ISP merely by a definition. The proof of this is that no mere definition can interfere with the interchangeability of “x” and “cons(car(x),cdr(x))”. So there is a firm distinction between having and not having assignment. To paraphrase Landin, the expressive power of assignment is evidenced by the fact that it can break the η-law for products (π1 M ; π2 M ) = M. Another law for product, sometimes called copyability, formally (λx:(x; x)) M = (M ; M )
was used by the second author to argue for the expressive power of callcc [22]. Our first main result continues that work by showing that an equivalence related to the copyability equivalence cannot be broken by exceptions, so that exceptions cannot be encoded using continuations. Conversely, our second main result is that two terms that can quite easily be distinguished with exceptions cannot be distinguished by continuations. The proof is technically the most novel of this paper and relies on the “answer type” of continuations being polymorphic and hence in some sense inaccessible.
2 Languages We begin with precise descriptions of the three languages, the call-by-value version of PCF (VPCF for short), the enhanced version with continuations (VPCF+C), and the enhanced version with exceptions (VPCF+E). 2.1 VPCF The core language is given by the grammar s ::= int j (s ! s) V ::= x j n j (λx : s: M ) M ::= V j (M M) j Ω j (rec f (x : s): M ) j (succ M ) j (pred M ) j (if0 M then M else M )
The syntactic category s ranges over simple types; the category V over values; and the category M over terms. A recursive function f with argument x is defined using the construct (rec f (x : s): M ), where M is the body of the recursive definition. VPCF includes a divergent term Ω, even though it is definable from recursion, to simplify certain proofs. Following standard conventions, we identify terms up to renaming of bound variables and use the notation M [x := N ] for the capture-free substitution of N for x in M [1]. Finally, to simplify notation, we often omit types when they can be reconstructed from the context, and use op for either the succ or the pred operations. Typing rules for VPCF appear in Table 1. In the judgement Γ ` M : s, a typing context Γ is a finite, partial function from variables to types with domain dom(Γ). Table 1. Typing rules for VPCF. [Var] [Op]
[Lam]
[Omega]
` x : Γ x x 2 dom Γ Γ ` M : int Γ ` op M : int Γ x:s `M:s Γ ` λx : s M : s ! s Γ`Ω:s Γ
( );
(
;
(
( )
)
1
1:
2
) ( 1
2)
[Int] [If]
[App]
[Rec]
Γ
` n : int
Γ ` M : int Γ`N :t ` if0 M then N else N : t Γ`M: s !s Γ`N :s Γ` MN :s Γ f : s !s x:s `M:s Γ ` rec f x : s M : s ! s i
Γ
(
( 1
(
( 1
;
(
2)
1
(
2) )
2 ); 1 ):
1
2
1 ) ( 1
2
2)
The operational semantics of VPCF is given using evaluation contexts [4]. The evaluation contexts are defined by the grammar E ::= [] j (E M) j (V E ) j (succ E ) j (pred E ) j (if0 E then M else M ) These contexts define a left-to-right, call-by-value evaluation strategy, and specify the location of the next subterm to be reduced. In other words, any closed, well-typed term can be parsed into an evaluation context and a redex in the hole of the context. The operational rules are E [(λx: P) V ] ! E [P[x := V ]] E [succ n] ! E [n + 1] E [pred 0] ! E [0] E [pred (n + 1)] ! E [n] E [if0 0 then M else N ] ! E [M ] E [if0 (n + 1) then M else N ] ! E [N ] E [rec f (x : s1 ): M ] ! E [M [ f := (λx : s1 : (rec f (x : s1 ): M ) x)]] For VPCF and its extensions, we use ! for the reflexive, transitive closure of Finally, we use a notion of contextual equivalence due to Morris [17]:
.
!
Definition 1. Suppose M ; N are VPCF terms such that Γ ` M : s and Γ ` N : s. Then M and N are contextually equivalent, written M V PCF N, if for any VPCF context C[] (a
term with zero or more holes) such that 0/ ` C[M ] : int and 0/ ` C[N ] : int, C[M ] ! n iff C[N ] ! n. This relation, appropriately subscripted, will also be used in the extensions of VPCF. 2.2 VPCF+Exceptions The enhanced language with exceptions, called VPCF+E, is obtained by making the following additions to the grammar of VPCF: s ::= : : : j (s exn) V ::= : : : j es M ::= : : : j (raise M M) j (handle M M M) The symbol es ranges over an infinite set of typed exception constants. In the handle construct, the first expression is the exception to be handled, the second is the handler, and the third is the code to be run once the handler is installed. The typing rules for VPCF+E are those for VPCF and [Exception]
Γ ` es : (s exn)
[Handle]
[Raise]
Γ ` M : (s exn) Γ`N:s Γ ` (raise M N ) : t
Γ ` M : (s exn) Γ ` N : (s ! t ) Γ ` (handle M N P) : t
Γ`P:t
The operational semantics is also an extension of that for VPCF. The grammar of evaluation contexts becomes E ::= : : : j (raise E M) j (raise V E ) j (handle E M M ) j (handle V E M ) j (handle V V E ) Let Ee denote the set of evaluation contexts that do not contain a handler for e along the spine. The operational rules are those for VPCF and 0 E [handle e V V 0 ] ! E [V ] 0 E [handle e V Ee [raise e V ]] ! E [V V 0 ]
2.3 VPCF+Continuations The language with continuations, called VPCF+C, is obtained by adding the following constructs to the syntax of types and terms: s ::= : : : j (s cont) M ::= : : : j (callcc M ) j (throw M M) The additional typing rules for the new constructs are the following: [Callcc]
Γ ` M : (s cont ! s) Γ ` (callcc M ) : s
[Throw]
Γ ` M : (s cont) Γ`N:s Γ ` (throw M N ) : t
The operational semantics requires a new class of values for continuations and additions to the grammar of evaluation contexts: V ::= : : : j (γx : s: E [x]) E ::= : : : j (callcc E ) j (throw E M) j (throw V E ) The operational rules are those for VPCF and ! E [P[x := γx : s: E [x]]] E [callcc (λx: P)] E [throw (γx : s: E 0 [x]) V ] ! E 0 [V ]
3 Exceptions cannot encode continuations One difference between exceptions and continuations can be explained on an intuitive level: once an exception is raised, a program cannot return to the site where the exception was raised. Continuations, however, can be “upward escaping” in the sense that they may be stored inside lambda abstractions and invoked multiple times. For instance, the following expression (see also [22]) argfc = callcc (λk: throw k (λx: throw k (λy: x))) grabs a continuation and immediately throws a functional value to that continuation. The continuation is, however, reinvoked whenever the function is called. This argument, as stated in the introduction, does not yield a formal difference; we need something more concrete to show the expressiveness of allowing “upward” continuations. The following proposition establishes that difference: Proposition 1. Let M be a VPCF+E term such that 0/ ` M : (int ! int). Let S = (λx : (int ! int): λp: p x x) D = (λx : (int ! int): λy : (int ! int): λp: p x y) Then (S M ) V PCF +E (D M M), but there is an M such that (S M ) 6V PCF +C (D M M). The proposition can be used to deduce that exceptions cannot express callcc and throw. Suppose, for instance, we could define closed VPCF+E terms having the same operational behavior as callcc and throw. Then the argfc function could be transliterated into VPCF+E, yielding a counterexample to the proposition. To see the (S M ) 6V PCF +C (D M M) part, pick M to be argfc. Let P = λx: λy: ((x 1); (y 2)) where (Q; N ) is an abbreviation for ((λd : N ) Q) with d a fresh variable. Then one may show ((S M ) P) ! 1 and ((D M M) P) ! 2. (This example can be easily tested in Standard ML of New Jersey.) We are left only with proving (S M ) V PCF +E (D M M). To carry out the proof, we define a way of relating terms containing occurrences of (S M ) and (D M M). Define a substitution to be a map from variables to terms. The necessary relation, called , is defined by induction:
– N N; – If N N 0 and P P0 and Q Q0 , then (N P) (N 0 P0 ), (λx: N ) (λx: N 0 ), (op M ) (op M ), (if0 N then P else Q) (if0 N 0 then P0 else Q0 ), and similarly for rec, handle, and raise; – If σ σ0 , then 0 0 (S σ(M )) (D σ (M ) σ (M )); where σ σ0 if the substitutions have the same domain, and for any variable x in the domain, σ(x) σ0 (x).
Table 2. Natural Semantics of VPCF+E
+V N +0
(succ N )
N
+0 P +R
(pred N )
+
N 0 1 then P1 else P2 )
(if0 N
+ λx P
N
+R
+ V P x: V +R +R N +V P +V Q +V handle N P Q + V N + raise e V op N + raise e V P + raise e V P Q + raise e V N + raise e V raise N P + raise e V N + raise e V handle N P Q + raise e V N + e P + V Q + raise e V e6 e handle N P Q + raise e V P
(
:
1)
1[
Q
=
00
(
)
(
)
)
0
(
)
(
0
(
0
(
)
)
(
00 ) 0
00 )
+
+
+
+
+
(raise e V 0 ) (V
+
N P Q)
+R
V 0)
+R
N (raise e V ) then P1 else P2 ) (raise e V )
+
N
+
+
+
+V P+
(raise
+
N P)
+
(raise e V 0 )
+ raise e V (
0)
N V P (raise e0 V 0 ) (handle N P Q) (raise e0 V 0 )
)
= 0
+n
P V Q (raise e V 0 ) (P Q) (raise e V 0 )
)
(
N)
(n + 1) P2 R then P1 else P2 ) R
+ e P +V Q + (if0 N
)
(
(n + 1)
(handle
)
)
0
N
)
(
(
( + 1)
N e P V N P) (raise e V )
)
(
+
(pred
(raise
)
(
(
(
]
00
+
(if0 N
(P Q)
0
(
+n +n
N
V
(rec
f (x : s): P)
+
+ λx : s P f : (
:
[
= (rec
f (x : s): P)])
To prove the proposition more easily, we also use a natural semantics [7]; this form of semantics rewrites a term M into a final answer in one step via a relation +. Let R range over results, which in this case are either values V or uncaught exceptions (raise e V ). Then define the relation + by the rules in Table 2. It is not hard to show that the natural semantics is, in essence, just a reformulation of the original operational semantics:
Lemma 1. Suppose 0/ ` P : s. Then P P + (raise e V ).
!
V iff P + V , and P
!
Ee [raise e V ] iff
Finally, we need a lemma stating the connection between terms related by . Lemma 2. Suppose P Q. – If P + V , then there is a V 0 such that Q + V 0 and V V 0 . – If Q + V 0 , then there is a V such that P + V and V V 0 . These lemmas give us enough machinery to prove the (S M ) V PCF +E (D M M) part of the proposition. Suppose M is a VPCF+E term such that 0/ ` M : (int ! int), and let S, D be as above. Suppose C[] is a VPCF+E context such that 0/ ` C[S M ] : int and 0/ ` C[D M M] : int. Note that C[S M ] C[D M M]. Thus, by Lemma 2, C[S M ] + n iff C[D M M] + n. By Lemma 1, C[S M ] ! n iff C[D M M] ! n.
4 Continuations cannot encode exceptions Continuations have a static flavor: if a continuation is reified and escapes, throwing to that continuation returns control to the point the continuation was reified. Exceptions, on the other hand, have a dynamic nature: raising an exception returns control to a handler that is determined dynamically. This turns out to give additional expressive power, in that a procedure may dynamically handle exceptions raised inside its arguments. To give evidence of this expressive power, we prove the following Proposition 2. Suppose F; G are closed VPCF+C lambda abstractions with 0/ ` F : / ` G : int ! int. For i = 1; 2, define (int ! int) ! int and 0 Pi = (F (λd : G 0; Ω)); (F (λd : i)) Then P1 V PCF +C P2 , but there are lambda abstractions F; G in VPCF+E such that P1 6V PCF +E P2 . To see P1 6V PCF +E P2 , define F = (λh: handle e (λd : 0) (h 0)) G = (λd : raise e 0) Then P1 ! 1 and P2 ! 2. The equivalence in VPCF+C can be explained intuitively. Essentially, there are just two cases: either F ignores its functional argument, in which case both Pi and Pj must return the same result, or F calls its argument. In that case, control must either escape (if G performs a callcc and a throw), or it must get stuck evaluating Ω in an infinite loop. A direct formalization of this intuition, using the techniques of the last section, proves elusive. Instead, the proof goes by applying a cps transform to VPCF+C. The target language is VPCF+α, formed by enhancing types with a new base type α: s ::= : : : j α
The type α has no additional term constructors for it, intuitively representing the abstractness of the answer type of continuations [14]. The abstractness of the answer type has not, to our knowledge, been exploited in quite the way the proof does here. The cps transform divides into a translation on types and on terms. For types, we define
int
int
=
( s ! t ) = (s (s cont) = (s s = (s
t) α) ! α) ! α ! !
where s represents the “values” of type s, and s represents the computations of type s; these notions are derived from Moggi’s account of side effects using the computational lambda calculus [16]. The cps transform on terms, given in Table 3, takes a derivation of the judgement Γ ` M : s to a derivation of the judgement Γ ` M : s, where Γ has the same domain as Γ, and Γ (x) = (Γ(x)) . Table 3. Cps transform from VPCF+C to VPCF+α.
Γ
[Int]
[Op]
[Lam]
[App]
[Rec]
[Callcc]
[Throw]
(
`
Γ Γ
)
:
(
(
( )
)
:
[Omega]
[If]
` λκ : s ! α κ x : Γ x ` λκ : int ! α κ n : int Γ ` λκ : s ! α Ω : s Γ ` M : int
Γ
[Var]
:
)
(λκ : int
Γ
(
:
))) : int
(
:
i (λm : int: if0 m then (N1
` λκ : int ! α M (
! α M λm : int κ op m ` M : int Γ ` N : s
:
m; κ fresh
κ) else (N2 κ))) : s
m; κ fresh
` ` λκ : s ! ! ! t κ fresh Γ `M : s!t Γ `N :s m n κ fresh Γ ` λκ : t ! α M λm : s ! t N λn : s m n κ : t Γ f : s !s x:s `M:s κ fresh Γ ` λκ : s ! s ! α κ rec f x : s M : s ! s Γ ` M : s cont ! s m κ fresh Γ ` λκ : s ! α M λm : s cont ! s m κ κ : s Γ ` M : s cont Γ `N :s m n κ fresh Γ ` λκ : t ! α M λm : s cont N λn : s m n : t Γ
(
Γ ; x : s M : t t ) α: κ (λx : s : M )) : (s
(
(
(
;
( 1
(
)
(
:
2
2)
(
(
It is not hard to prove
(
): 1
)
(
(
:
1 (
;
(
(
:
:
(
(
:
) :
(
( 1 )
)
(
)
)))
;
;
;
;
2
)) ( 1
) :
))
2) ;
) :
(
:
)))
Proposition 3. If Γ ` M : s is derivable, then Γ ` M : s is derivable. The proof goes by induction on the structure of the typing derivation. We can also prove, using techniques of Plotkin [19], that the translation is adequate: Theorem 1 (Adequacy). Consider any closed VPCF+C term M such that 0/ ` M : int. Let κ0 be a variable (assumed to be of type (int ! α)). Then M ! n iff (M κ0 ) ! κ0 n. The key lemma in proving the proposition is the following: Lemma 3. Suppose F; G are closed, recursion-free lambda abstractions in VPCF+C such that 0/ ` F : (int ! int) ! int and 0/ ` G : (int ! int). Let Pi = (F (λd : G 0; Ω)); (F (λd : i)) Then Pi V PCF +α Pj for any numerals i; j. Proof. Because F; G are lambda abstractions, simple β reduction shows that Pi V PCF +α λκ: F (λd : λκ: G 0 (λm: Ω)) (λm: F (λd : λκ: κ i) κ) Using a series of lemmas, we can show that F is equivalent, in the sense of V PCF +α , to one of the following forms: Ω (λx : (int ! int) : Ω) (λx : (int ! int) : x S) (λx : (int ! int) : λy : int ! α: Ω) (λx : (int ! int) : λy : int ! α: y n) (λx : (int ! int) : λy : int ! α: y Ω) (λx : (int ! int) : λy : int ! α: x S1 S2 ) The first, second, and fourth cases imply that for all i, Pi V PCF +α (λκ: Ω). The third and last cases imply that for all i, Pi V PCF +α (λκ: G 0 (λm: Ω)). The fifth case implies that for all i, Pi V PCF +α (λκ: κ n). Finally, the sixth case implies that for all i, Pi V PCF +α (λκ: κ Ω). In all cases, Pi is equivalent to a term that does not depend on i. Thus, for any numerals i; j, Pi V PCF +α Pj . By adequacy, Pi V PCF +C Pj . The proof of Proposition 2 now follows straightforwardly.
5 Conclusions and directions for further work For the typed languages we have given, we have shown that neither continuations nor exceptions can encode the other. While this may be expected by some, we know of no formal proof; in fact we were surprised by the subtlety of the proof and the failure of our initial brute-force attempts. Indeed, the superficial similarity between some uses of continuations and exceptions seems to have confused several authors: purported macroencodings of exceptions in terms of syntactic sugar for callcc have appeared in the literature [2, 18].
In addition to showing the impossibility of macro-encodings of one in terms of the other, we believe that the examples shed light on the nature of the difference between static and dynamic control. In particular, in the inequivalence for exceptions there are two functions which statically have no knowledge of each other in that they do not pass arguments or results to each other, and yet they can communicate via an exception. This may be seen as a failure of modular reasoning. Sitaram and Felleisen used an equivalence similar to the one in Section 4 as an argument for the additional expressive power of the “prompt”, or control delimiter [21]. We can regard this as evidence of the essentially dynamic nature of the prompt; compare dynamic-wind in Scheme [8]. They gave an informal argument for the validity of this equivalence for continuations alone, but no hint of a formal proof. The results here leave open a few issues. First, are there ways of showing that the various forms of exceptions cannot macro-express each other? The examples here concern only one form, closest in spirit to that of CAML; Standard ML has local exceptions, which seems more powerful, and LISP has no handlers, which seems less powerful. Second, do the results carry over to untyped languages? In fact, the proof that exceptions cannot encode continuations does work for the untyped versions of VPCF+E and VPCF+C; it seems less clear how to adapt the other proof to the untyped setting. The proof methods developed here are probably applicable elsewhere. For instance, one can show for the typed cps transform above that all recursion-free terms of cps type are representable as the target of recursion-free VPCF+C terms. This gives a kind of representation theorem, much along the lines given by Danvy and Lawall [3], except in the typed setting. An interesting consequence is that “prompts” are not in the target of the cps transform, and that fact may lead to a full abstraction theorem for VPCF+C.
References 1. H. P. Barendregt. The Lambda Calculus: Its Syntax and Semantics, volume 103 of Studies in Logic. North-Holland, 1981. Revised Edition, 1984. 2. G. M. Bierman. A computational interpretation of the lambda-mu calculus. In Proceedings of Symposium on Mathematical Foundations of Computer Science, number 1450 in Lecture Notes in Computer Science, 1998. 3. O. Danvy and J. L. Lawall. Back to direct style II: First-class continuations. In Proceedings of the 1992 ACM Conference on Lisp and Functional Programming, pages 299–310. ACM, 1992. 4. M. Felleisen. The theory and practice of first-class prompts. In Conference Record of the Fifteenth Annual ACM Symposium on Principles of Programming Languages, pages 180– 190. ACM, 1988. 5. M. Felleisen. On the expressive power of programming languages. Science of Computer Programming, 17:35–75, 1991. 6. J. Gosling, B. Joy, and G. Steele. The Java(tm) Language Specification. Addison Wesley, 1996. 7. G. Kahn. Natural semantics. In Proceedings Symposium on Theoretical Aspects of Computer Science, volume 247 of Lect. Notes in Computer Sci., New York, 1987. Springer-Verlag. 8. R. Kelsey, W. Clinger, and J. Rees. The revised5 report on the algorithmic language Scheme. Available from http://www.neci.nj.nec.com/homepages/kelsey/r5rs.ps.gz, 1998.
9. P. J. Landin. A generalization of jumps and labels. Report, UNIVAC Systems Programming Research, August 1965. 10. P. J. Landin. A generalization of jumps and labels. Higher-Order and Symbolic Computation, 11(2), 1998. 11. Xavier Leroy and Pierre Weis. Manual de reference du language Caml. InterEditions, Paris, 1998. 12. M. Lillibridge. Exceptions are strictly more powerful than call/cc. Technical Report CMSCS-95-178, School of Computer Science, Carnegie Mellon University, 1995. 13. M. Lillibridge. Uncaught exceptions can be strictly more powerful than call/cc. HigherOrder and Symbolic Computation, 5:275–307, 1999. To appear. 14. A. R. Meyer and M. Wand. Continuation semantics in typed lambda-calculi (summary). In R. Parikh, editor, Proceedings of the Conference on Logics of Programs, 1985, Lecture Notes in Computer Science 193, pages 219–224. Springer-Verlag, 1985. 15. R. Milner, M. Tofte, R. Harper, and D. MacQueen. The Definition of Standard ML (Revised). MIT Press, 1997. 16. E. Moggi. Notions of computation and monads. Information and Control, 93:55–92, 1991. 17. J. H. Morris. Lambda-calculus models of programming languages. Technical Report MACTR-57, M.I.T. Lab. for Computer Science, 1968. 18. C.-H. L. Ong and C. A. Stewart. A curry-howard foundation for functional computation with control. In Proceedings of ACM SIGPLAN-SIGACT Symposium on Principle of Programming Languages, Paris, January 1997. ACM Press, 1997. 19. G. D. Plotkin. Call-by-name, call-by-value and the λ-calculus. Theoretical Computer Sci., 1:125–159, 1975. 20. J. C. Reynolds. The discoveries of continuations. Lisp and Symbolic Computation, 6(3/4):233–247, November 1993. 21. D. Sitaram and M. Felleisen. Reasoning with continuations II: Full abstraction for models of control. In Proceedings of the 1990 ACM Conference on Lisp and Functional Programming, pages 161–175. ACM, 1990. 22. H. Thielecke. Using a continuation twice and its implications for the expressive power of call/cc. Higher-Order and Symbolic Computation, 1998. 23. A. van Wijngaarden. Recursive definition of syntax and semantics. In T. B. Steel, Jr, editor, Formal Language Description Languages for Computer Programming, Proceedings of an IFIP Working Conference, pages 13–24, 1964.