GunterRR95 - A Generalization of Exceptions and Control in...

Info iconThis preview shows pages 1–12. Sign up to view the full content.

View Full Document Right Arrow Icon
Background image of page 1

Info iconThis preview has intentionally blurred sections. Sign up to view the full version.

View Full DocumentRight Arrow Icon
Background image of page 2
Background image of page 3

Info iconThis preview has intentionally blurred sections. Sign up to view the full version.

View Full DocumentRight Arrow Icon
Background image of page 4
Background image of page 5

Info iconThis preview has intentionally blurred sections. Sign up to view the full version.

View Full DocumentRight Arrow Icon
Background image of page 6
Background image of page 7

Info iconThis preview has intentionally blurred sections. Sign up to view the full version.

View Full DocumentRight Arrow Icon
Background image of page 8
Background image of page 9

Info iconThis preview has intentionally blurred sections. Sign up to view the full version.

View Full DocumentRight Arrow Icon
Background image of page 10
Background image of page 11

Info iconThis preview has intentionally blurred sections. Sign up to view the full version.

View Full DocumentRight Arrow Icon
Background image of page 12
This is the end of the preview. Sign up to access the rest of the document.

Unformatted text preview: A Generalization of Exceptions and Control in ML-like Languages Carl A. Gunter University of Pennsylvania gunter©cis.upenn.edu Abstract We add functional continuations and prompts to a language with an ML—style type system. The operators significantly extend and simplify the control operators in SML/NJ, and can be themselves used to implement (simple) exceptions. We prove that well-typed terms never produce run-time type errors and give a module for implementing them in the latest version of SML/NJ. 1 Introduction Exceptions and continuations are two common means of al— tering control in mostly functional call-by-value languages, even in statically typed languages. All dialects of the ML language, for instance, build in an exception mechanism, in- cluding the ML of the LCF theorem prover [7], CAML [11], and Standard ML (SML) [13]. The exception system of, 6.9., SML, consists of three operations: one for declaring new ex— ceptions, one for “raising” an exception (possibly with a value), and one for “handling” raised exceptions. The sec— ond construct, the continuation mechanism, can be added in principle to any dialect of ML [3], but a full scale imple— mentation is to our knowledge only a part of SML/NJ, the New Jersey implementation of SML. The continuation mech— anism consists of two primitives: callcc (call-with-current- continuation) which reifies the control stack as a function and passes it to another function, and throw which invokes a continuation on an argument. Both mechanisms, particu— larly the exception mechanism, are useful: exceptions can be used to recover from errors in an efficient, elegant, and un— cluttered way, and continuations can be used to implement other control features, 6.9., concurrency [19]. It is folklore (the authors know of no published proof) that neither exceptions nor continuations can be expressed as a macro in terms of the other (at least if no references are present), even though they are closely related. In this paper we provide a generalization of simple exceptions and continuations in an ML-style type system. We prove that the language is type—safe, 216., evaluation of programs cannot generate run-time type errors. There are two interesting and important aspects of the type system. First, unlike the type system of SML/NJ, our system requires no new type con- Didier Rémy INRIA, Rocquencourt Didier.Remy@inria.fr Jon G. Riecke AT&T Bell Laboratories riecke©research.att.com structor for continuations (we introduce one for prompts). Second, the semantics of the language overcomes some of the anomalies of callcc in the top—level interactive loop, and allows a somewhat cleaner style of programming than callcc. The assignment of types for exceptions is generally well understood, but the assignment of types for continuation— based operations is not. Sitaram and Felleisen [24] were the first to give a limited type system for continuation—based operations. They added callcc into PCF, a simply-typed language with basic arithmetic, and used the typing rule Al—a:(T—>nat)—>T( callcc) A [- (callcc a) : 7' where A specifies the types of free variables. The limitation in the typing is obvious: only a continuation whose result type is nat can be reified. In essence, the problem of typing callcc in PCF hinges on the fact that one must pick one return type for continuations. The type mat is a canonical choice in PCF, but not in languages with more type structure. In a language with ML-style polymorphism, callcc ought to have a polymor- phic type with an arbitrary return type. Duba, Harper, and MacQueen [3] have proposed adding a unary type construc- tor cont for typing callcc which hides the return type of the continuation. The type for callcc is1 callcc : (’1a cont —> ’1a) —> ’1a in their proposal, which is the type given in the SML/NJ implementation. To invoke the continuation, one uses the operation throw : ’a cont —> ’a —> ’b Although there are only two type variables in the type of throw, in actuality three types are necessary to explain the type of throw. For instance, in the expression ==> 5 > (1 + callcc (fn k => if s = "a" then throw k 2 else size 5)) where ==> denotes the “prompt” of the interactive loop, there is the argument type int of the continuation, which must be the same as that expected by the context in which it was reified; there is the type int of the context in which the throw is invoked; and there is the type bool of the value returned to the prompt after a value is throw7n to the reified 1Note that the type variable is imperative [8]; in SML/NJ this is indicated by the number (“weakness”) in the variable 71a. For the moment, the reader need not worry about imperative variables. continuation. The third type is not directly represented in the types for callcc and throw, but is rather “hidden” in the abstract type constructor cont. The failure to represent the prompt type can lead to difficulty with the operational behavior of callcc. From a theoretical standpoint, what Wright and Felleisen [28] call “strong soundness77 fails to hold. A language satisfies strong soundness if the type obtained from evaluating an expression is the type assigned statically; the absence of run— time type errors is what Wright and Felleisen term “weak soundness”. One can see why the issue of strong soundness arises in the following session of the SML/NJ interactive loop: ==> val c = callcc (fn k=> fn x=> throw k (fn y=> x+4)); val c = fn 2 int -> int ==> fung () = 5 > (c 2); val g = fn 2 unit -> bool The value of g () should be a function that returns 2 + 4 given any integer (and probably also resets the value of g to the previously declared value, if any), even though the type system predicts the type bool. The same behavior would happen any time a continuation is stored in some data structure like a closure or reference cell. SML/NJ regards this as an anomaly, and resolves the problem by placing “prompt stamps” on reified continuations and aborting with a runtime exception if a continuation is invoked under a prompt that does not match its stamp. Our approach to typing callcc is simpler: we force the missing prompt type to be included in the type of the reified continuation. If we were to modify the constructor cont, the continuation k above would have a type like (int —> int) cont (int —> int) where the right side is the type of the prompt. This informa- tion could be used in the typing of an expression that throws to this continuation. For instance, a top-level phrase yield- ing a value of boolean type in which a value is throw’n to k must be rejected as having a type error. A logical extension of this idea is to allow the programmer to insert explicit con— trol points representing his own prompts. This idea is not new; Felleisen introduced first—class prompts in the untyped setting It simplifies matters to resume execution at the control point marked by the prompt, thus leaving the is— sue of whether to resume the computation within the reified control to the program. To make this work with types in the way outlined above, such a reification must carry the type of the enclosing prompt. Our design is intended to make it possible to check the correctness of this type statically. We achieve this by requiring that prompts be typed and namedi for Felleisen7s original prompts, in contrast, there is only a single, untyped prompt The reified control fragments can then be treated as functionsithat is, we do not need the type constructor cont, only the function type construc- tor ->. Before beginning the formal treatment, let us see how one example works. To begin with, we create a new prompt by a gensym-like primitive operation new_prompt: ==> val p = new_prompt 0: int prompt val p 2 int prompt This prompt can be set at control points expecting an inte- ger and used to delimit a control fragment that returns an integer. Two more primitives are required: (set p in a) which sets prompt p in expression a, and the primitive (cupto p as k: in b), which reifies the control up to p and binds this to k in the expression 1). Thus, ==> 5 > (set p in 1 + (cupto p as k in 2 + (k 3))) val it = false : bool binds to k the control 1 + L] (the control up to the point where the prompt was set), 216., an int—expecting, int— returning continuation), and evaluates (2 + (k 3)) in the control context 5 > [.1 (not 5 > 1 + [.J). When k is in— voked as a function with 3 as its argument, the expression 5 > 2 + 1 + 3 is evaluated to false. The reification k is treated as the ordinary function fn x => 1 + x. Notice how similar these operations are to exceptions and callcc, e.g., control behavior as provided by callcc is achieved by setting a prompt at top level. In fact, the operation that reifies the continuation is a typed version of Felleisen7s “functional continuation” operator .7 [4], an op— erator that captures continuations as functions whose ap- plication does not necessarily abort the computation. In terms of macro-expressiveness, .7: can express callcc and other control operators. The new_prompt and set opera— tions, though, have direct analogs in the exceptions of ML: new_prompt declares a new prompt just like the keyword exception generates a new exception value, and set marks a breakpoint on the call stack just as try in CAML or handle in SML marks a breakpoint (although these have a handler associated with them). After first describing the syntax and operational semantics of our language and proving that the language is type safe, we show how to express a general— ization of callcc and simple exceptions, and show how to implement the operations in a manner as efficient as the implementation of callcc. 2 A Typed Language with Prompts Table 1 defines the grammar of the language. The syntax is that of a restricted version of the core of ML (without base constants, references, and exceptions) with three ex— tra constructs for manipulating the control flow of a pro- gram: new_prompt, set _ in _, and cupto _ as _ in _. The language is based on primitive syntax classes of variables at and prompts p. The construct new_prompt returns a fresh prompt; set _ in _ establishes a new dynamic extent for the prompt to which the first subexpression evaluates and runs the second subexpression; and cupto _ as _ in _, where cupto is an abbreviation for “control upto”, reifies a continu— ation. The cupto operation binds its second subexpressioni which must be a variableito the control up to the value of its first subexpressioniwhich much be a promptiin the scope of its third. Binding conventions for the A—calculus portion of the language are the usual ones; we identify all terms up to renaming of bound variables. We use the nota- tion a[b/ac] to denote the capture—free substitution of term b for variable x in term a. The typing rules for the language are given in Table 2. Here, A stands for a type context whose syntax is given in Table 1. The operation close(A, 7') returns a type scheme (Val . ..0zn.7'), where {a1,...,0zn} is the set of type vari- ables occurring free in 7' but not in A. The syntax re— stricts the expression bound by let to be a value, i.e., an expression that causes no immediate subcomputation. The type system becomes unsound if the syntax of let is left unrestricted. This phenomenonifirst pointed out by Tofte [25, 26] in the context of typing references in MLi has been well—documented in the case of continuations (cf. [8, 12, 28]). The type system adopts Wright’s proposal [27] 1::Va1...ozn.T€A Al—x:T[7'1/oz1,.. A l- new_prompt : (unit —> 7' prompt) Al—a1: Al—seta1 inagzr 0421: ’U 22: T21: 0' II: A[x:T0]|-a:7'1 A l- (Amn) : (7'0 —> 7'1) (7' prompt) set p in Ep[cupto p as m in a]/P ’U (a1 02) letval m = U in a set a1 in (12 cupto cm as x in a2 a: O neW_prompt (Ax. a) 1’ oz unit (7' —> 7') (T prompt) V041...ozk.7' UllA[m:a]|A[p:T] (Var) .,Tn/ozn] (Fun) AFUQIT (Prompt) Table l: Syntax Expression Value Application Polymorphic let binding Set a prompt Reify control upto a prompt Value Variable Unit value Generate new prompt Abstraction Prompts Type Type variable Unit type Function type Type of prompts Type scheme Typing context Table 2: Typing Rules. AI- : unit AI-vzn (Unit) Al—a1: Al—aliT2—>T1 Al—(a1a2) (7'1 prompt) pzTEA Al—p: (7' prompt) Al—a227'2 (App) 2T1 A[m : (To —>T1)] l-az : T1( AI- cupto (11 as x in (12:70 A[$ : close(A77'1)] |- az : 7'2 ( Al—letvalxzvinazn Table 3: Operational Semantics. [—l | (E a) | (v E) lsetEinalsetpinE |cupto E as x ina Evaluation context Hole Application Set Cupto Redex reductions ((Amc) v)/P —>red let val a: = v in a/P —>Ted (new_prompt —>red set p in v/P —>red —>red alU/fEl/P alU/JEl/P P/{P} UP U/P (ME-a) (Ag-EplylVP Context reductions ao/Po —>red ai/Pi E[a0]/P0 —> E[a1]/P1 p¢P (Prompt Const) Cupto) Let) (which was well-known to Tofte) of restricting the form of let to “value—only polymorphism” rather than adding im— perative type variables as the SML definition does. For bet- ter readability we use the syntactic sugar (let in = al in (12) for ((Amag) a1) for the monomorphic let. Some familiar facts follow immediately from the form of the type system. It is not hard to see that every term has a unique typing deriva- tion, and that one may easily derive an algorithm (based on unification) that derives a principal type. A rewriting semantics in the style of [4] (a convenient reformulation of structured operational semantics [15]) is given in Table 3. The semantics is given in two parts: the first part defines a collection of evaluation contexts, which specify the positions in which a redex can be reduced, and the second part specifies a collection of rules defining a bi— nary relation —>red for the reduction of redexes. Intuitively, to do a step of evaluation on a term a, one finds a context E and a redex a0 such that a E E[a0] and a0 —>Ted a1; then E[a0] —> E[a1]. Our redex reductions are of the slightly more complex form ao/Po —>Ted al/Pl, meaning “the redex (10 with prompts P0 reduces to expression a1 with prompts P1” so reductions in an evaluation context have the form E[a0]/P0 —> E[a1]/P1. The set Piithe current set of allocated promptsiis much like a “store” in an opera- tional semantics of references, and determines the previously allocated prompts. Thus, the expression (new_prompt al- locates a “fresh prompt” relative to the current P. Also, in the redex rules, the notation Ep denotes an evaluation context in which the hole is not in the scope of a setting of prompt p. The rules specify how to reify a continuation and pass a value up to the nearest dynamically enclosing prompt. A few examples should make the behavior of the reduc— tion semantics more apparent. For instance, the expression let x : new_prompt in set CL‘ in cupto CL‘ as k: in (k first allocates a fresh prompt, sets the dynamic scope to be this prompt, reifies the (empty) continuation as k, and passes to k the identity function. The final result is thus the identity function. At a high level, the formal steps are (let x : new_prompt in set x in cupto x as k in (k: / 9) —> set p in cupto p as k in (k / {p} —> (Akk / {p} —> (Ow-51?) (AZ-Z» / {p} —> (AZ-Z) / {p} This expression is also well-typed in the language: the vari- able x has type ((a —> oz) prompt) and the continuation k has type ((a —> oz) —> (a —> (1)). Another example is that of an abortive computation: let x = new_prompt in set x in cupto x as k in (Az.)\y.y) which aborts the computation and passes (A2. Ag. y) to the top—level. There is actually more latitude in assigning operational semantics to the language than it first appears. For instance, any of the following rules preserve the strong type soundness theorem below: set p in Ep[cupto p as CL‘ in a]/P —>Tad set p in ((Aara) (Ay.Ep[y]))/P set p in Ep[cupto p as CL‘ in a]/P —>Tad set p in ((Aara) (Ay. set p in The first rule grabs the functional continuation but leaves the prompt p set in the continuation; this corresponds to the operational semantics of Felleisen7s .7: operation The second rule also leaves the prompt p set, but also grabs the “set” when the functional continuation is reified; this corre- sponds to the operational semantics of Danvy and Filinski7s shift operation It is easy to see how to simulate the first rule in our semantics by adding a set before every body of a cupto. Similarly, the second rule can be simulated using the first. The other direction, though, seems not to be knowni that is, whether the weaker operational rules can simulate the operational semantics we have given to cupto. There are even further possibilities, including ones that erase all inter— vening set’s during a cupto [14]. We do not feel, though, that there is a clear answer to the question of which opera— tional rule is right; sufiice it to say that we have picked one, and that the other rules lead to strong type soundness as well. 3 Type Safety We now show that reduction preserves typing and each well- typed term never gets stuck at a run—time type error. Type safety is a subtle issue because “getting stuck at a run—time type error” is open to interpretation. Some exam— ples of “run-time type error” require little justification. For instance, the non—well—typed term ((neW—PromPtO) (MW—Prompt()))/Vj cannot be reduced past a form (p1 p2)/{p1,p2} for some prompts p1,p2; the result is obviously a run-time type er- ror because of the attempt to apply a non—function to an argument. But the issue is subtle in the presence of control operations, and for our purposes not every “stuck” term will be a run-time type error. For instance, the well-typed term let x :new_prompt in cupto a: as k in k / 0 reduces to (cupto p as k: in with no further reduc- tions possibleithe continuation cannot be reified since no prompt has been set. The situation for exceptions in ML is similar: well—typed terms can still result in an “uncaught exception”. In general one must accept the situation, 6.9., the restrictions required to make it possible to determine statically that there is no division by zero would probably be unacceptable. We leave aside these concerns and adopt an analog to the ML convention, i.e., the term above does not represent a run—time type error. Theorem 9 provides a precise expression of our assumptions. We first need a few simple lemmas about the type sys— tem that are essentially independent of control operations. Proofs of the following lemmas can be found in, e.g., [18]. Lemma 1 (Type Substitution) IfA l- a : 7', then A[7'0/a] l- a : T[To/a]. Lemma 2 (Extension of Type Assignment) Let B be any type assignment whose domain contains no free variables ofa. ThenABl—azr ifi‘Al—azr. A type scheme Val . . . an. 7' is more general than a type scheme Val . . . cap. 7' if there are types 71, “.77, such that T[T1/O(1] . . . [Tn/an] is equal to 7". Similarly, a type assign- ment A is more general than a type assignment B if they have the same domain D and, for all x E D the value A(x) of A at an is more general than Lemma 3 (Generalization of Type Assignment) IfA is more general than B and B l- a : 7', then A l- a : 7'. Lemma 4 (Term Substitution) Suppose A l- ao : 7'0 and A[r : V071...ozn.7'0] l- a : 7', where a1,...,0zn are not free in A. Then A l- a[a0/$] : 7'. The proof of type safety for our particular language re- quires a few definitions. A type assignment A is a prompt assignment if A 2 @[pl : 7'1] . . . [pn : 7n], and A] is a prompt extension of a prompt assignment A if A’ is of the form AA" where A" is a prompt assignment. Evaluation of expressions may create new prompts but cannot change the type of an expression; thus, we write ao/Po C al/Pl if P1 contains P0 and, for any prompt assignment A0 with prompts P0 and any type 7' such that A0 I- ag : 7', there ex- ists a prompt extension A1 of A0 such that P1 is the domain of A1 and A1 I- a1 : 7'. It is not hard to see that the relation C is reflexive and transitive. One may also easily prove the following lemma by induction on the structure of evaluation contexts. Lemma 5 [fag/Po C al/Pl, then E[a0]/Po C E[a1]/P1. The important step of reduction is the capture of the current context up to a prompt. The context E used in a program will be turned into a function Ar. The following lemma will simplify the corresponding case in the proof of subject reduction. Lemma 6 Suppose A l- E[a1] : 7'. Then there erists a type 7'0 such that A l- al : 7'0 and, for any term a2 such that A |- az : 7'0, we also have A l- E[a2] : 7'. Proof: The proof is by induction on the form of the evalua— tion context; it relies on the fact that the hole in an evalu- ation context is not in the scope of any binding Operation. Here are three typical cases: Case E = Case E = (E’ a0): From the hypothesis we know that A l- E'[a1] : 7'1 —> 7' and A l- ao : 7'1. Thus, by the induction hypothesis, there is a type 70 such that A l- al : 7'0, and, for any a2 such that A |- az : 7'0, we have A l- E’[a2] : 7'1 —> 7'. The statement now follows from the typing rule App. Then pick 7'0 to be 7'. Case E = (set E’ in a0): From the hypothesis, we have A l- E'[a1] : (7' prompt) and A l- ag : 7'. Thus, by the induction hypothesis, there is a type 7'0 such that A l- al : 7'0, and, for any a2 with A l- ag : 7'0, we have A l- E'[a2] : (7- prompt). The statement now follows from the typing rule Set. I Lemma 7 (Redex Contraction) If aO/Po —>Ted al/Pl, then ao/Po C al/Pl. Proof: Each case of redex reduction can be considered in— dependently. Assume there is a prompt assignment A0 with prompts P0, a type 7' such that A0 I- oo : 7'; we need to exhibit a prompt extension A1 of A0 such that P1 is the domain of A1 and A1 I- al : 7'. Case a0 : ((Aaru) u) or (let val a: = u in a): In both cases the reduction steps for these forms do not change the set of prompts. In each case, there exists a type 71 and a list W of type variables not in the free variables of A0 such that A0 I- 71:71 and Ada : VW.7'1] l- a : 7'. Since a1 : a[u/$], it follows from Lemma 4 that A0 I- al : 7'. Case a0 = (new_prompt Note that A0 |- new_prompt : (unit —> 7" prompt) and A0 I- : unit. Suppose a1 = p where p ¢ P0. If P1 = P0 U {p} and A1 = A0[p : 7"], then A1 I- al : (7" prompt). Case (10 : (set p in u): Trivial. Case a0 = (set p in Ep[cupto p as r in a]): The reduc- tion step for setting a prompt does not introduce any new prompts. Thus, A0 |- Ep[cupto p as r in a] : 7' and A0 |- p : (7- prompt). Applying Lemma 6, there exists a type 71 such that A0 I- (cupto p as r in a) : 7'1 (1) and A0 |- Ep[a1] : 7' for any expression a1 such that A0 I- al : 7'1 From (1) it follows that A0[x : 7'1 —> 7'] l- a : 7' and, consequently, A0 I- Aaco : (71 —> 7) —> 7'. Let y be a variable that appears neither in the domain of A0 nor in Ep. By (2) and Lemma 2, A0[y : 7'1] - Ep[y] : 7', and hence A0 I- (Ay. Ep[y]) : (7'1 —> 7'). Thus, A0 - (Am.a)(}\y.Ep[y]) : 7' follows. I Theorem 8 (Subject Reduction) If ao/Po —> al/Pl, then ao/Po C al/Pl. Proof: A simple combination of Lemmas 5 and 7. I Note that Theorem 8 does not hold without the value—only restriction (or other restrictions on polymorphic let); see [8, 12, 28] for examples. Theorem 9 (Value Halting) Suppose A is a prompt as- signment with prompts P. If A l- a : 7' and a/P cannot be reduced, then a is either a ualue or a term of the form Ep[cupto p as x in a']. Proof: The proof is by induction on the size of a. The cases when a = 0, new_prompt, p, and (Am. a') are trivial, so con- sider the remaining cases: Case a = (a1 a2): There must be a type 7'2 such that A l- al : (7'2 —> 7'). By the induction hypothesis applied to al/P, a1 either is a value or has the form Ep[cupto p as a: in a’]. The latter implies a has the form Ep[cupto p as x in a'], so consider the case when a1 is a value. By the induction hypothesis applied to (lg/P, a2 either is a value or has the form Ep[cupto p as a: in a’]. Again, the latter case means that the lemma holds, so consider the case when a2 is a value too. Note that al cannot be an abstraction, since a cannot be reduced. Since a1 has a functional type, it can only be new_prompt. Hence, a2 is of type unit, and it must be the value However, this is not possible since a cannot be reduced. This rules out all cases but the case when a has the form Ep[cupto p as x in a'], so the statement holds. Case a = (let val a: = u in a1): Then a could be re— duced, contradicting the hypothesis. Case a = (set a1 in a2): Then A l- a1 : 7' prompt and A l- 0,2 : 7'. If a1 is not a value, then it has the form Ep[cupto p as x in a0], and therefore a is also of the form Echpto p as a: in a0] where E; = (set E], in (12). If (11 is a value, a1 must be a prompt q. Note that a2 cannot be a value, for otherwise a could be reduced. Thus, a2 must be Ep[cupto p as r in ac] where p # q (otherwise a can be reduced). It follows that a = E;[cupto p as a: in a0] where E; = (set q in Ep). Case a : (cupto a1 as a: in a2): Then A l- al :7'1 prompt and A[r : 7'0 —> 7'1] |- az : 7'1. If a1 is not a value, it must be of the form Ep[cupto p as y in a0] and so is a. Otherwise, it must be a prompt q and EF must not set q. Thus a is of the form Eq[cupto q as a: in (12] where Eq : Ep. I The following theorem then follows immediately from the previous two theorems: Theorem 10 (Type Safety) Suppose (Z) l- a : 7'. Then one of the three following must happen: 1. There exists a value v and a prompt assignment A with prompts P such that a/Ql —>* u/P and A l- u : 7'; 2. a/@ —>* Ep[cupto p as r in a’]/P; or 5’. The reduction sequence starting from (1/0 is infinite. 4 Expressiveness 4.1 Simple exceptions Simple exceptions are a simplification of the exception mechanism found in most ML variants. We add three new syntactic forms to our language: new_exn. which generates a new internal name for an exception; (raise a1 a2). which raises an exception a1 with value a2; and (handle a1 a2 a3). which evaluates a1 to exception h and a2 to 112. and then evaluates a3 so that if exception h is raised with a value v. the evaluation of a3 aborts and handler 222 is applied to v. To describe the formal semantics, we need internal exception names h. new evaluation contexts aise u E) | (raise E a) | (r ’) | (handle 2) E a) | (handle 2) u’ E) (handle E a a and new redex rules (new_exn ())/X.P —>..ed U X.P h ¢ X (handle h u u/)/X.P —>..ed u//X.P (handle h u Eh[raise h u/D/X. P —>..ed (u ul)/X. P where X is a finite set of exceptions and E. is an evaluation context with no intervening (handle h u" E) expressions. The new operations can also be typedinot surprisinglyi using typings similar to those in ML. If we add a new type construction (7' exn) to the syntax of types. the types of the new operations are — (New Exception) A l- new_exn : (unit —> T exn) fl (Exception Const) A l- h : (T exn) Al—a1:(rexn) Al—a2: T (Raise) A I- (raise a1 a2) : T0 Al—a1:(rexn) Al—a2 : (T—>7'0) Al—asiTo (Handle) A l— (handle a1 a2 a3) 2 To It is a simple exercise to extend the proof of Theorem 10 to the enhanced language. Simple exceptions differ from exceptions in most ML variants in three ways. First. exceptions are generated from new_exn rather than declared by the keyword exception. This difference is inconsequential. since one may use let to bind an exception to a name. Second, one may not handle multiple exceptions in one handler. Again. the difference is inconsequential. since one may use multiple handle expres— sions to yield the same effect. Third. handlers must be given with respect to a specific exception. For example. in most ML variants one can write (handle _ a2 a3) that catches any exception raised during the evaluation of agieven one that is declared in a3. This difference is substantive; wild- card patterns are a useful feature. giving the programmer the ability to recover from arbitrary errors. On the other hand. wildcard exceptions encourage some sloppiness in er— ror handling. cause problems for reasoning about code. and prevent certain compiler optimizations (John Reppy. per— sonal communication. August 1994). Simple exceptions are a redundant feature in our lan- guage; we do not know about ML handlers with wildcard expressions without assuming an extensible datatype in the language? That is. one may easily macro expand the three primitives for simple exceptions into our base language with- out exceptions but with new_prompt. set. and cupto (i.e.. simple exceptions do not change the “expressiveness”. in the sense of [5]. of the language). Let [[a]] be the notation for the translation of a term with simple exceptions to one with- out. The translation of new_exn is simply new_prompt. i.e.. [[new_exn]] = new_prompt. The translation of (raise a1 a2) is let 1'1 2 [[a1]] in let x2 = flag] in cupto 361 as k in 362 where $1.3m. k are distinct fresh variables. The translation of the term (handle (11 a2 a3) is let 1'1 2 [[a1]] in let x2 = flag] in let p=new_prompt in set p in (A2. (cupto p as k in ($2 (set $1 in let 1'3 2 [[0,3]] in cupto p as k in 123) where m1. $2. 2.1). k are distinct fresh variables. The trans- lation of an exception constant h is a prompt constant with the same name. Finally. the translation is homomorphic in all of the other operations. e.g.. [[(al a2)]] = ([[a1]] [[a2]]). The translation preserves types. although this must be stated with some care. Let [[7]] be the type 7' with every occurrence of (To exn) replaced recursively by (IITofl prompt). Theorem 11 Suppose A l- a : 7' in the ertended language. and [[A]] is the type content with all types translated. Then W t M 1 H- The proof is a simple induction on the original typing deriva- tion. For instance. consider a = (handle a1 a2 as) where a1 has type 7'. a2 has type (To exn). and a3 has type (To —> 7'). To assign the right type to [[a]]. let p : prompt); then one may assign the type [[7]] to the cupto expression. It is harder to state and prove a correctness theorem for the translation with respect to reduction: the translation of simple exceptions into the cupto mechanism means that one must keep careful track of which cupto7s and prompts belong to simple exceptions and which are in the program itself. For instance. in translating the term set p in handle h (Ax. (cupto p as k: in 2An extensible datatype is an ML datatype where new construc— tors can be added later. Indeed. the type ern is such an extensible datatype in several implementations of ML, e.g.. CAML or SML. One can then simulate full exceptions with a unique “exception” car— rying values of type earn and the wildcard handler becomes a regular handler. some of the cupto’s belong to the handle but one does not. To maintain the proper bookkeeping, we use a relation a/X, P B a’/P’ where a is a term possibly involving simple exceptions and a’ is a term without simple exceptions. The definition implies that a/X, P B [[a']]/X UP, although more terms are related. One can then prove the following Theorem 12 Suppose A is an emception context with em— cepttons X and B is a prompt context with prompts P, and AB |- a : 7' in the emted language, and a/X,P B a’/P’. Then a/X,P —>* v/X0,P0 tfi‘ a’/P’ —>* u’/P0’ where U/X0,P0 B U’/P6. The theorem holds if the base language is extended with other ML constructs such as if—then-else and numeric con- stants. 4.2 Callcc One could also add primitive operations for reifying and invoking continuations. Since the language has first-class prompts, the most natural way to add callcc to the lan— guage is to add the forms (callccp a1 a2), (throwp a1 a2), and (abortp a1 a2). The first arguments to callccp and abortp are the prompts delimiting the continuation to be reified or discarded. The semantics may be formalized by enhancing the grammar of evaluation contexts with (callccp E a) and the redex rules with (set p in Ep[callccp p a])/P —>Ted (set p in Ep[a (Am. abortp p Ep[x])])/P (throwp U1 U2)/P —>T6d (U1 ’UQ)/P (set p in Ep[abortp p a])/P —>red a/P These operations can also be given types via the rules Al—alszrompt Al—a2:7' (Abortp) A l- abortp a1 a2 : 7' A l- al : 7'0 prompt A l- ag : (7' —> 7'0) —> 7' (Callccp) A - callccp a1 a2 : T Al—alzT—H'o Al—a2:T(ThrOWp) A - throwp a1 a2 : T0 Note the difference between the typing rule for throw in [3] and SML/NJ and the typing rule for throwp here. A throw expression in SML/NJ has any type. Syntactically this is achieved by hiding the return type of the continuation subexpression (the first subexpression). This is (weakly) se— mantically sound because of the global invariant that all con- tinuations captured with callcc start by aborting the compu— tation and resuming somewhere else. Hence, it is clear that the type of throw a1 a2 may be any type since the com— putation of this expression will never return to the calling context. A throwp expression, however, explicitly mentions the return type T0 of the continuation. In fact, the typing rule is exactly the same as for function application, which is how the operation is implemented. As with simple exceptions, adding these continuation op— erations is merely adding syntactic sugar; one can translate terms with them to terms without. For instance, the trans— lation of a term (abortp a1 a2), denoted [[abortp a1 (12] as above, is let 1'1 2 [(11] in cupto 1:1 as k in [m] The definition of [[callccp a1 a2] is let x1 =[[a1]] in cupto 361 as k in get $1 in (k ([[agfl (Ax.abortp (k Finally, [[throwp a1 a2] = ([[a1]] [[a2]]). The translation is correct, t.€., the analogs of Theorems 11 and 12 hold for this translation. 5 Implementation To complete the argument that prompts and cupto are sim- pler and easier to use than callcc, we show that the cupto can be implemented as efficiently (in an asymptotic sense) as callcc. Our operationsiincluding multiple promptsican be im— plemented as a module in SML/NJ with the signature in Table 4. Other implementations of functional continuation operators appear in the literature: for instance, Filinski [6] shows how to encode control operators with callcc and one reference cell under the assumption that there is one prompt. The module provides a way to translate complete programs in our language to SML programs. The module has three primitives new_prompt, set and cupto that implement the constructs of the same name. Since there is no macro facility in SML, the user of the module must adopt the conventions [[set cu in a2] = set [[a1]] (fn () => [[a2]]) [[cupto (11 as x in (12] = cupto [[all] (fn x => [[a2fl) The other constructs of our language can be translated di— rectly into SML/NJ, e.g., = and 1 [Darn] = (fn x =>[[a]]). Appendix A gives an implementation in SML/NJ. Our en— coding is clearly of the same flavor as the untyped encoding of shift and reset [22] into Scheme with callcc, but it is not easy to relate them in a precise way, since the languages that they encode are also different. Notice that the signature involves weak type variables (cf. [10, 26]). If SML were modified to have value-only poly- morphism, the signature of this module would be identical but without the “weaknesses” on the type variables. The weaknesses never cause a problem in translating programs in our language to SML, since our language has “value-only” polymorphic let. The encoding of multiple prompts is more difficult than that of a single prompt, since we have to push on the same control stack continuations to prompts of possibly different types. The elements of the stack are variant types. Since we do not know in advance the number of prompts that will be defined, we need an open variant type. Some ML variants, among them SML, provide one built-in extensible datatype of exception values. The generativity of exception decla— rations allows one to define a function that returns a new exception (holding a value of fixed but arbitrary type) each time it is called. Any locally extensible datatype could re- place the use of exceptions; we never use exception handling or raising except for reporting errors. This is a practical jus- tification for giving extensible datatypes full status in ML; we have met a more theoretical justification in Section 4. Assuming that we already have an efficient implemen— tation of callcc, i.e., the cost of callcc and throw are Table 4: Signature for Prompts. signature PROMPT = Sig exception Uncaught_prompt type ’a prompt val new_prompt : unit —> ’1a prompt (* to report uncaught prompt *) val set : ’la prompt -> (unit -> ’la) -> ’la val cupto : ’1a prompt —> ((’2b —> ’1a) —> ’1a) —> ’2b end constant, the encoding yields an efficient implementation of cupto. The conditions are clearly met by most cps compila- tion strategies, such as those used in SML/NJ. Given such an efficient implementation, the cost of new_prompt and set is clearly constant. The cost of cupto may, however, be proportional to the size of the control stack. The cost of applying a continuation is also proportional to the small piece of control stack stored in the closure of that continu- ation; a similar cost, of course, exists with the more tradi— tional throw. Each of the above operations takes constant time for programs that use a single prompt. In particular, this is the case for all programs with only callcc, whether they have been straightforwardly rewritten with cupto7s, or linked with a module that implements callcc and throw with cupto’s. Moreover, the small factor by which the cost is increased in the simulation might be compensated by the conciseness of programs using cupto rather than callcc. For stack-based compilation strategies, callcc is an ex- pensive operation; since our simulation relies on callcc, the simulated operations will also be expensive. We could easily extend the simulation to implement a “restoring” cupto, so that the common pattern (cupto x as k: in set x in k: a) is directly implemented in terms of callcc. Programs using only callcc should run about as fast with primitive callcc as with callcc simulated with restoring cupto. Replacing callcc by cupto thus should not decrease performance. On the other hand, it is quite difficult to predict whether programming with functional continuations would be more efficient than programming with usual, aborting continua- tions. The obvious reason is that such a judgement depends on programming styles and therefore it is very subjective. Another important factor is the quality of the implementa— tion of continuations. Clearly, one cannot obtain better performance when sim— ulating functional continuations with aborting continuations. When setting a prompt, the simulation reifies the current continuation (16., copies the current stack) and continues with a quasi-empty stack. Later, when control is reified up to the prompt, the current stack represents the context up to the prompt. A primitive implementation of cupto7s would certainly mark the stack when setting a prompt so that copying the context from the root to the prompt is avoided. Since continuations can express callcc, one may pro- gram with functional continuations using only total continu— ations. Capturing a functional continuation cannot be faster than capturing a total continuation of the same size. How— ever, in many examples (see the next section), callcc7s come in pairs with some little protocol that actually implements functional continuations. Thus, we now consider the prob- lem of comparing programs written with cupto7s with their counterpart in terms of callcc. That is, we compare the efficiency of our simulation to a primitive implementation of cupto. For sake of simplification, we consider a restoring cupto and the following scenario (context and stack can be inter— changed, according to which one provides better intuitions): the execution starts in an empty stack which grows to a control point E1 where a prompt is set. The evaluation con- tinues with al and reaches a control point E1[set p in E2[_]] where the context up to p is reified as k: and evaluation con- tinues (16., the mark and the context k are left on the stack) with (12. The whole program is of the form E1[set p in m] where (11 is itself E2[cupto x as k in set x in k (12]. The comparison of performances naturally depends on the quality of the compilation of continuations. Let us call an implementation “naive” if it always copies the part of stack corresponding to the context that is reified. With a naive implementation of continuations, primitive functional continuations are clearly more efficient than simulated ones: both E1 and E2 are copied by the simulation while only E2 is copied with a primitive implementation. There are also “smart” implementations that copy the stack lazily, 216., just before the stack is popped. Given support from the garbage collector, this may avoid copies of reified con- tinuations that have become unreachable at the time when copying should occur. We do not know whether smart com- pilation would equally benefit to both the simulated and the primitive cupto. It might also be the case that if prompts are set frequently, cutting up the stack would become un— necessary in the case of prompts, i.e., a naive primitive im- plementation of cupto might run as efficiently as a smart implementation of callcc. Queinnec and Serpette have described an implementa— tion of functional continuations [17] that never copies the stack. Roughly, their idea is to freeze some active part of the stack, and jump over that part until it becomes garbage. However, their semantics differs from ours since prompts are erased from the context during reification (see Section 7). It is not clear that their compilation schema can be applied to our semantics, and, if the schema can be applied, whether one obtains good performance. Moreover, their method re— quires garbage collection on the stack and it penalizes block allocation. This makes the implementation closer to a stack— less implementation, and performance should be comparable to the case of CPS—implementations. 6 Programming Examples In this section we briefly describe two small examples that give the flavor of how to use our operations and the im— plementation in SML/NJ: traversing a tree and coroutines. Nothing about the examples is really original or complex: in— deed7 neither example makes use of multiple7 named prompts in the same way that the encoding of exceptions does. Nev— ertheless7 the examples do illustrate how one programs with functional continuations in a typed setting. 6.1 Traversing a Tree The first example7 a small toy example suggested to us by Matthias Felleisen7 demonstrates how functional continua- tions can express computations more succinctly than they can be expressed using callcc. Given an element of the SML datatype datatype tree = Null I Cell of int I Pair of tree * tree of binary trees with values at all internal nodes7 we want to write two functions : tree -> int : unit —> int get_first get_next that walk down the tree and output the values of the nodes, one at a time. Answers are elements of the datatype datatype answer = None I Some of int For instance, given the tree val tree = Pair (Pair (Cell 1, Null), Pair (Cell 2, Cell 3)) the following output is required: - get_next (); val it = None : answer - get_first tree; val it = Some 1 : answer - get_next (); val it = Some 2 : answer - get_next (); val it = Some 3 : answer - get_next (); val it = None : answer There is a very simple and generic solution7 provided two functions start and suspend that7 respectively7 start the toplevel computation and suspend the evaluation of the com— putation7 returning to toplevel7 and a reference resume7 that contains the suspended computation where to resume next: fun walk Null = None I walk (Cell i) = suspend (Some i) I walk (Pair (t1,t2)) = (walk t1; walk t2) fun get_first t = start (fn () => walk t) fun get_next () = start (fn () => !resume None) The implementation of the functions start and resume is straightforward with first-class prompts and functional con- tinuation operations: local val toplevel = new_prompt(): answer prompt in val resume = ref (fn (xzanswer) => x) fun start f = set toplevel f fun suspend v = cupto toplevel (fn k => ((resume := k); v)) end This can be encoded directly into SML/NJ using callcc instead of our control operations without going through our implementation. local val exit = ref (fn (xzanswer) => x) in val resume = ref (fn (xzanswer) => x) fun start f = callcc (fn toplevel => (exit := throw toplevel; f(); !exit None)) fun suspend v = callcc (fn rest => (resume := (fn x => throw rest x); !exit V)) end While the encoding with prompts and functional continua— tions is rather natural7 the encoding with aborting contin- uations is difficult to write (especially if written directly) and also harder to understand. The example illustrates the coupling of the two aborting continuations exit and resume that are replaced by a single functional continuation resume in the cupto version of the code. 6.2 Coroutines The tree traversal example is not convincing: one can solve the problem by producing answers in a lazy stream without using continuations at all. The simulation of coroutines is a more interesting and practical example. Suppose we want to implement the following signature of coroutines: signature CDRDUTINES = sig val coroutine: (unit —> unit) —> unit val fork: (unit -> unit) -> unit val yield: unit —> unit val exit: unit -> unit end The function coroutine establishes a context for running coroutine expressions; the other operations are the obvious functions. For instance7 one would write coroutine (fn () => (fork (exit); exit())) for forking a trivial process and then exiting7 which would yield control to the child process just created. The implementation uses an internal prompt to estab- lish the scope of the coroutine function, and relies on the simulation above (Section 5) and a module for queues: structure Coroutines:COROUTINES = struct open Prompt open Queue val p = new_prompt (): unit prompt val readyQ:(unit —> unit) queue = kaueue () fun coroutine f = set p f fun dispatch () = (dequeue readyQ ()) handle Dequeue => () fun exit () = cupto p (fn k => coroutine dispatch) fun yield () = cupto p (fn k => (enqueue (readyQ,k); coroutine dispatch)) fun fork f = enqueue (readyQ,fn () => (f (); exit ())) end Notice that the state of a running coroutine is saved as a function in the queue when doing a yield. The implementation of the same policy for fork using callcc is more complicated, and it seems to require a trick. The following code is slightly modified from [20]: structure CoroutineszCDRDUTINES = struct open Queue val readszunit cont queue = kaueue () fun coroutine f = (f ()) handle Dequeue => () fun dispatch () = throw (dequeue readyQ) () fun exit () = dispatch () fun fork f = let val newThread = callcc (fn k1 => (callcc (fn k2 => (throw k1 k2)); f (); exit ())) in enqueue (readyQ, newThread) end fun yield () = callcc (fn k => (enqueue (readyQ,k); dispatch ())) end The two callcc’s are necessary to get the right behavior from fork. The parent process must grab a continuation that represents the child process’s continuation and put the child7s continuation in the queue. The continuation k1 is the continuation that sets newThread to the child continu— ation. puts the continuation into the queue. and continues. The continuation k2. on the other hand. is the continuation that calls f and exit and continues with the rest of the pro— gram. which is the child7s continuation. This pattern of two callcc7s is quite common: the implementation of Concur— rent ML. for instance, uses 4 instances of double callcc7s (8 total) out of 28 callcc’s. Implementing a different forking policyiwhere the child starts immediatelyiis easier fun fork f = callcc (fn oldThread => (enqueue (readyQ , oldThread) ; f 0; exit ())) but is also quite easy using cupto: fun fork f = (enqueue (readyQ,fn () => (f 0; exit ())); yield ()) Functional control operators make the implementation of coroutines simpler because one can separate the manage- ment of queues from the processes by prompts. 7 Comparison with Previous Work We have already seen. in Section 2. how the operational se- mantics of our control operations compares with Felleisen7s .7: and Danvy and Filinski’s shift operation. Many other choices of functional continuation operations are possible. 6.9.. Hieb and Dybvig7s spawn [9] and Queinnec and Ser- pette’s splitter [17]. See [16. 14] for a detailed comparison of the operational semantics of these operations. With one exception. none of these papers consider type systems for functional continuations. The sole exception is Queinnec and Serpette’s paper [17] on splitter. abort. and call/pc. These operations differ in some respects from our three operations of new_prompt. set. and cupto. Using a notation similar to ours. the types of the operations are splitter : (7' prompt —> 7') —> 7' abort : T prompt —> (unit —> T) —> T0 call/pc : 7' prompt —> ((To —> 7') —> 7'0) —> To The splitter operation sets a new prompt and runs the body. If abort is ever called with that prompt and an argu— ment (a thunk). the prompt is erased and the thunk is called in the continuation before the splitter. If call/pc is ever invoked with a function. the continuation up to the prompt is reified and all its internal prompts are unset before it is passed as an argument. Using our notation and operations for clarity. the operational semantics can be expressed by the rules (splitter a)/P —>Ted (set p in (a p))/P U {p}. p g P (set p in Ep[abort p (1]) —>red (a (set p in Ep[call/pc p a]) —>.ed (set p in (Ema (Ax.Ep[x1)1> where stands for the context E where all prompts have been unset. 7.6., (set p in E) is E and the transformation is homomorphic on other constructs (only prompts can be in the position of p. since set _ in _ expressions are all in— troduced by the reduction rule for splitter). We do not know if they proved a type soundness theorem as we have: the paper [17] does not state the theorem nor attempt to prove it. but using our proof technique it is easy to carry out. Appart from this significant difference. Queinnec and Serpette7s splitter also comes closest to ours in adding multiple prompts. Others. notably Sitaram and Felleisen [22] and Danvy and Filinski [2]. have added multiple prompts and control operations to languages to obtain more control. The difference between these operations and our language (and Queinnec and Serpette7s) is important: prompts in our proposal are hidden in an abstract type that only the com- piler can manipulate. whereas in [2. 22] the representations of prompts are known to the programmer (as integers). The hidden representation of prompts is essential for implement— ing exceptions in a correct manner: the implementation gen- erates fresh prompts that programmers cannot cupto. Also. having a special type of prompts makes it easy to incorpo- rate prompts into a language like ML; we otherwise would need some cumbersome naming scheme for infinite sets of prompts at each type. Aside from the rigorous treatment of types. the single identifiably new feature in our proposal is the decomposition of declaring a new prompt from setting a prompt. and the corresponding ability to set a prompt more than once. This is again used in our encoding of exceptions. but we know of no other natural examples which require one to set a prompt more than once. 8 Discussion We have shown how to incorporate primitives for first-class prompts and the reification of control up to a prompt in a statically-typed language. Let us consider briefly the the- oretical. programming. and compilation issues related to these primitives. We believe that the primary theoretical benefit of using named. typed prompts arises in the simple proof of strong soundness. In fact. our choice of constructs can be used to simplify proofs of strong soundness for other control opera- tions. For instance. to prove strong soundness for callcc. Wright and Felleisen [28] consider only expressions that do not contain an abort. Given their way of expressing the semantics of callcc. this is essentially equivalent to ruling out expressions containing continuations reified relative to a different top-level. The restriction works because any con- tinuations reified in the course of the evaluation of a given expression must all be relative to the top-level for that ex- pression. Our typing gives a way to explain the strong soundness for callcc more perspicuously: when the user types an expression. the interactive top-level loop simply creates a fresh prompt (with the type of the expression) and set’s; all callcc’s are then done via cupto’s to this fresh prompt. To determine whether named. typed prompts are useful in programming requires some experience in writing pro— grams. In the untyped case. prompts add significant expres- sive power [23. 21]; we believe the examples of [21] could be typed in our system. We also conjecture that many applications that currently uses callcc (such as various threads packages or CML) could benefitifor instance. the explicit prompt mechanism may simplify the implementa— tion of threads in a interactive top-level loop. At the very least, the sense in which callcc can be easily encoded in our language should ensure that switching to explicit prompts will cost little. A challenge left open by this work is still an efficient direct implementation of the operations. especially for stack— based compilation strategies. Although our operations have better typing and pro— gramming properties than callcc in a language like ML. there is still the larger question of whether inexpensive. continuation-based operations are really necessary. Concur- rency operations can be easily built using continuations. but there are not very many other good examples of programs that need continuations. and continuations are difficult to use for the non-expert programmer. It may well be that concurrency primitives are more fundamental and impor— tant than continuation operations. but until the right set of primitives is found it may be best to build in continuation operations. Acknowledgements: We thank Andrew Appel for discussions about how “prompts” are encoded in the SML/NJ inter- active top—level loop. Bruce Duba. Trevor Jim. and Chris— tian Queinnec for several helpful discussions. and Matthias Felleisen and Tim Griffin for detailed comments on drafts. A Implementation of Prompts with Callcc in SML/NJ The following code implements the signature of Table 4. structure Prompt : PROMPT = struct exception Uncaught_prompt exception Core_dumped type ’a cont = ’a General.cont val throw = General.throw val callcc = General.callcc type ’a control = ’a cont * exn list type ’a prompt = {In : ((bool * ’a cont)—>exn), Out: ((bool * ’a cont)->’a cont * exn list) —> (exn —> ’a control) -> exn -> ’a control} fun new_prompt () = let exception Sharp of (bool * ’1a cont) fun In (b,c) = Sharp (b,c) fun Out yes no x = case x of Sharp p => yes p I 2 => no 2 in ({In = In, Out = Out} : ’1a prompt) end The key idea of this encoding is to use a stack of control points (called pc for “prompt and continuation”): val stack = ref (: exn list) fun push pa = stack 2= pc :2 !stack fun pop () = case !stack of => raise Uncaught_prompt I pc :2 rest => (stack := rest; pc) To set a prompt. the current continuation is captured and transformed into a control point associated with prompt p that is pushed on the control stack. The expression is run and control resumes at the control point found on top of the control stack. which must be a p prompt. fun set (p: ’1a prompt) e = callcc (fn normal_cont => let val _ = push (#In p (true,normal_cont)) val v = e() val (effective_continuation, _) = #Dut p (fn (b,C) => (c,[l)) (fn sc => raise Core_dumped) (pop()) in throw effective_continuation V end) When capturing control. the stack of control will be copied up to the corresponding prompt. Fake prompts (b is false) are ignored. fun pop_control (pz’a prompt) = let fun pop_it control = #Out p (fn (b,c) => if b then (c, control) else pop_it (#In p (b,c):2 control)) (fn pc => pop_it (pc :2 control)) (pop()) in pop_it end When a control is used as a function the saved control stack will be appended to the top of the current control stack. fun push_control (pc :2 control) = (push pc; push_control control) I push_control I] = 0 In more detail. the “cupto” first captures the control stack up to the first occurrence of the prompt p. and retrieves the continuation abort to jump to this point. Then it cap- tures the current continuation x. aborts to the prompt and keeps running in an environment where control stands for the following function: when given a value v it captures the current continuation as after and pushes it on the control stack as a fake p prompt. since it is used as a return address. but not to stop control. Then. the saved control is pushed on the control stack and computation jumps to position x. Later. when reaching prompt p. computation will resume at position after instead of abort. fun cupto p f = let val (abort, control) = pop_control p in callcc (fn x => throw abort (f (fn V => callcc (fn after => let val _ = push (#In p (false, after)) val _ = push_control control in throw x v end)))) end end (* struct *) References [1] [2] [10] [11] Andrew W. Appel. Compiling with Continuations. Cambridge University Press, 1992. Olivier Danvy and Andrer Filinski. Representing con- trol: A study of the cps transformation. Mathematical Structures in Computer Science, 2(4):?)617391, 1992. Bruce F. Duba, Robert Harper, and David MacQueen. Typing first-class continuations in ML. In Conference Record of the Eighteenth Annual ACM Symposium on Principles of Programming Languages, pages 1637173. ACM, 1991. Matthias Felleisen. The theory and practice of first- class prompts. In Conference Record of the Fifteenth Annual ACM Symposium on Principles of Program— ming Languages, pages 1807190. ACM, 1988. Matthias Felleisen. gramming languages. ming, 17:35775, 1991. On the expressive power of pro- Science of Computer Program- Andrzej Filinski. Representing monads. In Conference Record of the Twenty—First Annual ACM Symposium on Principles of Programming Languages, pages 4467 457. ACM, 1994. Michael J. C. Gordon, Robin Milner, and CR Wadsworth. Edinburgh LCF: A Mechanical Logic of Computation, volume 78 of Lect. Notes in Computer Sci. Springer—Verlag, 1979. Robert Harper and Mark Lillibridge. ML with callcc is unsound, July 1991. Message sent to the 77sml77 mailing list. Robert Hieb and R. Kent Dybvig. Continuations and concurrency. In Second ACM SICPLAN Symposium on Principles and Practice of Parallel Programming, pages 1287136, 1990. My Hoang, John C. Mitchell, and Ramesh Viswanathan. Standard ML-NJ weak polymorphism and imperative constructs. In Proceedings, Eighth An- nual IEEE Symposium on Logic in Computer Science, pages 15725, 1993. Xavier Leroy. The Caml Light system, release 0.6 7 Documentation and user’s manual. INRIA, 1993. In— cluded in the Caml Light distribution. [12] [13] [14] [15] [18] [19] 20 21 22 23 24 [25] [26] [27] [28] Xavier Leroy. Polymorphism by name for references and continuations. In Conference Record of the Twen- tieth Annual ACM Symposium on Principles of Pro— gramming Languages, pages 2207231. ACM, 1993. Robin Milner, Mads Tofte, and Robert Harper. Definition of Standard ML. MIT Press, 1990. The Luc Moreau and Christian Queinnec. Partial continua- tions as the difference of continuations: A duumvirate of control operators. In Sixth International Symposium on Programming Languages, Implementations, Logics and Programs, 1994. Gordon D. Plotkin. A structural approach to op- erational semantics. Technical Report DAIMI FN— 19, Aarhus Univ., Computer Science Dept., Denmark, 1981. Christian Queinnec. A library of high level control op— erators. Lisp Pointers, 1993. Christian Queinnec and Bernard Serpette. A dynamic extent control operator for partial continuations. In Conference Record of the Eighteenth Annual ACM Sym— posium on Principles of Programming Languages, pages 1747184. ACM, 1991. Didier Rémy. Extending ML type system with a sorted equational theory. Research Report 1766, Institut Na— tional de Recherche en Informatique, BP 105, F-78 153 Le Chesnay Cedex, 1992. John H. Reppy. Higher-order concurrency. PhD the— sis, Computer Science Department, Cornell University, Ithaca, NY, January 1992. Available as Cornell Tech— nical Report 92-1285. John H. Reppy. Concurrent Programming in ML. Cam- bridge University Press, 1995. To appear. Dorai Sitaram. Handling control. In Proceedings of the 1995’ ACM Conference on Programming Language De— sign and Implementation, pages 1477155. ACM, 1993. Dorai Sitaram and Matthias Felleisen. Control delim— iters and their hierarchies. Lisp and Symbolic Compu— tation, 3(1):67799, 1990. Dorai Sitaram and Matthias Felleisen. Control delim— iters and their hierarchies. LISP and Symbolic Compu— tation, 3267799, 1990. Dorai Sitaram and Matthias Felleisen. Reasoning with continuations II: Full abstraction for models of control. In Proceedings of the 1.990 ACM Conference on Lisp and Functional Programming, pages 1617175. ACM, 1990. Mads Tofte. Operational Semantics and Polymorphic Type Inference. PhD thesis, Edinburgh University, 1988. Mads Tofte. Type inference for polymorphic references. Information and Computation, 89(1):1734, 1990. Andrew K. Wright. Polymorphism for imperative lan- guages without imperative types. Technical Report COMP TR93-200, Department of Computer, Rice Uni- versity, 1993. Andrew K. Wright and Matthias Felleisen. A syntactic approach to type soundness. Information and Compu— tation, 115238794, 1994. ...
View Full Document

This note was uploaded on 02/13/2012 for the course CS 91.531 taught by Professor Giam during the Fall '09 term at UMass Lowell.

Page1 / 12

GunterRR95 - A Generalization of Exceptions and Control in...

This preview shows document pages 1 - 12. Sign up to view the full document.

View Full Document Right Arrow Icon
Ask a homework question - tutors are online