This preview shows pages 1–12. Sign up to view the full content.
This preview has intentionally blurred sections. Sign up to view the full version.
View Full DocumentThis preview has intentionally blurred sections. Sign up to view the full version.
View Full DocumentThis preview has intentionally blurred sections. Sign up to view the full version.
View Full DocumentThis preview has intentionally blurred sections. Sign up to view the full version.
View Full DocumentThis preview has intentionally blurred sections. Sign up to view the full version.
View Full DocumentThis preview has intentionally blurred sections. Sign up to view the full version.
View Full Document
Unformatted text preview: A Generalization of Exceptions and Control in MLlike 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 signiﬁcantly
extend and simplify the control operators in SML/NJ, and
can be themselves used to implement (simple) exceptions.
We prove that welltyped terms never produce runtime 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 callbyvalue 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 (callwithcurrent
continuation) which reiﬁes 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 efﬁcient, 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 MLstyle type system. We prove that
the language is type—safe, 216., evaluation of programs cannot
generate runtime 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
ﬁrst to give a limited type system for continuation—based
operations. They added callcc into PCF, a simplytyped
language with basic arithmetic, and used the typing rule Al—a:(T—>nat)—>T( callcc)
A [ (callcc a) : 7' where A speciﬁes the types of free variables. The limitation
in the typing is obvious: only a continuation whose result
type is nat can be reiﬁed. 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
MLstyle 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 reiﬁed; 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 reiﬁed 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
difﬁculty with the operational behavior of callcc. From
a theoretical standpoint, what Wright and Felleisen [28]
call “strong soundness77 fails to hold. A language satisﬁes
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 reiﬁed 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 reiﬁed
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 toplevel 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 ﬁrst—class prompts in the untyped
setting It simpliﬁes 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 reiﬁed
control to the program. To make this work with types in the
way outlined above, such a reiﬁcation 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 reiﬁed 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 gensymlike 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 reiﬁes 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 reiﬁcation 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 reiﬁes 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 macroexpressiveness, .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 ﬁrst 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 deﬁnes 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 ﬂow 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 ﬁrst subexpression evaluates and
runs the second subexpression; and cupto _ as _ in _, where
cupto is an abbreviation for “control upto”, reiﬁes a continu—
ation. The cupto operation binds its second subexpressioni
which must be a variableito the control up to the value of
its ﬁrst 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 phenomenoniﬁrst 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 AIvzn (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)] laz : 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 (MEa) (AgEplylVP Context reductions ao/Po —>red ai/Pi E[a0]/P0 —> E[a1]/P1 p¢P (Prompt Const) Cupto) Let) (which was wellknown to Tofte) of restricting the form of
let to “value—only polymorphism” rather than adding im—
perative type variables as the SML deﬁnition 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
uniﬁcation) 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
ﬁrst part deﬁnes a collection of evaluation contexts, which
specify the positions in which a redex can be reduced, and
the second part speciﬁes a collection of rules deﬁning a bi—
nary relation —>red for the reduction of redexes. Intuitively,
to do a step of evaluation on a term a, one ﬁnds 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 ﬁrst allocates a fresh prompt, sets the dynamic scope to
be this prompt, reiﬁes the (empty) continuation as k, and
passes to k the identity function. The ﬁnal 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} —> (Ow51?) (AZZ» / {p}
—> (AZZ) / {p} This expression is also welltyped 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 ﬁrst 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 ﬁrst 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 reiﬁed; this corre
sponds to the operational semantics of Danvy and Filinski7s
shift operation It is easy to see how to simulate the ﬁrst
rule in our semantics by adding a set before every body of a
cupto. Similarly, the second rule can be simulated using the
ﬁrst. 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; suﬁice 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 “runtime type error” require little justiﬁcation. 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 runtime 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 runtime type error. For instance, the welltyped 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 reiﬁed 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 ﬁrst 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 iﬁ‘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 deﬁnitions. 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 reﬂexive 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 inﬁnite. 4 Expressiveness 4.1 Simple exceptions Simple exceptions are a simpliﬁcation 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 ﬁnite 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) ﬂ (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 speciﬁc 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 = ﬂag] 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 = ﬂag] 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 (IIToﬂ 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
deﬁnition 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 tﬁ‘ 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—thenelse and numeric con
stants. 4.2 Callcc One could also add primitive operations for reifying and
invoking continuations. Since the language has ﬁrstclass
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 ﬁrst arguments to callccp and
abortp are the prompts delimiting the continuation to be
reiﬁed 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 ﬁrst 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 deﬁnition of [[callccp a1 a2] is let x1 =[[a1]] in
cupto 361 as k in get $1 in (k ([[agﬂ (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 efﬁciently (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 => [[a2ﬂ) 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 ﬂavor 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 modiﬁed to have valueonly 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 “valueonly”
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
deﬁned, we need an open variant type. Some ML variants,
among them SML, provide one builtin extensible datatype
of exception values. The generativity of exception decla—
rations allows one to deﬁne a function that returns a new
exception (holding a value of ﬁxed 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
tiﬁcation for giving extensible datatypes full status in ML;
we have met a more theoretical justiﬁcation in Section 4. Assuming that we already have an efﬁcient 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 efﬁcient 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 stackbased 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 difﬁcult to predict whether
programming with functional continuations would be more
efﬁcient 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 reiﬁes the current
continuation (16., copies the current stack) and continues
with a quasiempty stack. Later, when control is reiﬁed
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
efﬁciency of our simulation to a primitive implementation of
cupto. For sake of simpliﬁcation, 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 reiﬁed 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 reiﬁed. With a
naive implementation of continuations, primitive functional
continuations are clearly more efﬁcient 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 reiﬁed con
tinuations that have become unreachable at the time when
copying should occur. We do not know whether smart com
pilation would equally beneﬁt 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 efﬁciently 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 reiﬁcation (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 brieﬂy describe two small examples that
give the ﬂavor 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 ﬁrst 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 ﬁrstclass 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 difﬁcult 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 modiﬁed 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 reiﬁed 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 signiﬁcant 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 inﬁnite sets of
prompts at each type. Aside from the rigorous treatment of types. the single
identiﬁably 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 ﬁrstclass
prompts and the reiﬁcation of control up to a prompt in a
staticallytyped language. Let us consider brieﬂy the the
oretical. programming. and compilation issues related to
these primitives. We believe that the primary theoretical beneﬁt 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 reiﬁed relative to a
different toplevel. The restriction works because any con
tinuations reiﬁed in the course of the evaluation of a given
expression must all be relative to the toplevel 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 toplevel 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 signiﬁcant 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 beneﬁtifor instance. the
explicit prompt mechanism may simplify the implementa—
tion of threads in a interactive toplevel 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 efﬁcient
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.
continuationbased 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 difﬁcult to
use for the nonexpert 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 Grifﬁn 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” ﬁrst captures the control stack
up to the ﬁrst 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 ﬁrstclass 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 ﬁrst
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 MLNJ 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.
Deﬁnition 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, F78 153
Le Chesnay Cedex, 1992. John H. Reppy. Higherorder concurrency. PhD the—
sis, Computer Science Department, Cornell University,
Ithaca, NY, January 1992. Available as Cornell Tech—
nical Report 921285. 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 TR93200, 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.
 Fall '09
 Giam

Click to edit the document details