Lectures onType Theory
Chapter 35
Chapter 35Optional

Control Operators and Classical Proofs

Prerequisites. Direct starred prerequisites: none. No later core chapter depends on this route.

An exception discards the rest of a computation. It cannot save that rest, pass it to a function, and invoke it twice. A continuation is a value representing that remaining computation; it can be saved, passed to a function, and invoked more than once. A control operator captures or invokes continuations, enabling early return, backtracking, and coroutines; under propositions as types it also validates classical principles which have no intuitionistic proof.

The mechanism reifies the current call-by-value evaluation context as a value. Under propositions as types this value implements double-negation elimination, and hence excluded middle.

A stack that can become a value

The complete control-machine and proof-term rule sheets are collected in subappendix A.32; the main text derives the rules needed by each worked trace.

We write e0e for one root contraction. We extend the call-by-value simply typed calculus of chapter 2. Products, sums, the empty type, unit, and arrows retain their earlier rules. We add the base type Nat, its numerals, and, for the calculations below, addm:NatNat, a primitive value with addmn0m+n. This harmless constant avoids hiding control behind an arithmetic encoding.

We write π1,π2 for the earlier fst,snd, and write a case with named inl and inr branches. Only the typography changes; the retained typing and reduction rules do not. We retain 0 and 1 for the inherited empty and unit types, and write () for the earlier unit value .

Definition 35.1 — The continuation calculus λ _ K

The new type and surface terms are A::=ContA,e::=letcc k:ContA in ethrow e1 to e2. The statics adds

Γ,k:ContAe:A
Γletcc k:ContA in e:A
T-Letcc
Γe1:AΓe2:ContAΓB type
Γthrow e1 to e2:B
T-Throw

The inherited judgments include type formation ΓA type and the variable rule Var. Contexts are finite maps; weakening and exchange are admissible, and an assumption may be used more than once. Thus every premise of a displayed rule is read under the same ambient context. The result type B of a throw is arbitrary because evaluation never returns to the throw site.

A run-time value may additionally be cont(K), where K is a stack. This form is not source syntax. The complete run-time value grammar is v::=()naddmλx:A.ev,vinlvinrvcont(K). A frame is one layer of an evaluation context, and a stack is a finite sequence of such frames. Their grammars are F::=[]ev[][],ev,[]πi[]inl[]inr[]case [] of {inlxe1;inrye2}abortA([])throw [] to ethrow v to [],K::=K;F. A state Ke, read “K evaluates e,” evaluates a term; a state Kv, read “K returns v,” returns a value to the top frame.

If ContA is read operationally as an object-language representation of a refutation of A, T-Letcc is the classical step known as consequentia mirabilis: from a derivation of A under a temporary refutation of A, conclude A. Removing T-Letcc, T-Throw, and Cont leaves the inherited intuitionistic simply typed calculus.

The pure transitions are written out because the stack captured by letcc includes precisely these frames. Values change the direction of the machine: KvMReturnKv. Application and pairs use Ke1e2K;[]e2e1,MAppFunK;[]e2v1K;v1[]e2,MAppArgK;(λx:A.e)[]vKe[v/x],MBetaKe1,e2K;[],e2e1,MPairL(e1,e2 is not a value)K;[],e2v1K;v1,[]e2,MPairRK;v1,[]v2Kv1,v2,MPairRetKπieK;πi[]e,MProjK;πi[]v1,v2Kvi.MProjRet Injections first evaluate their payload. A case first evaluates its scrutinee, then selects and substitutes: KinleK;inl[]e,MInl(e is not a value)K;inl[]vKinlv,MInlRetKinreK;inr[]e,MInr(e is not a value)K;inr[]vKinrv,MInrRet and, writing B for the displayed pair of case branches, Kcase e of BK;case [] of Be,MCaseK;case [] of BinlvKe1[v/x],MCaseLK;case [] of BinrvKe2[v/y].MCaseR Empty elimination evaluates its impossible premise: KabortA(e)MAbortK;abortA([])e. There is no return transition for this frame, because no well-typed value has type 0. Unit, numerals, lambda abstractions, and primitive functions are values. The primitive arithmetic return is K;addm[]nMAddK(m+n).

Only the following transitions mention control: Kletcc k:ContA in eKe[cont(K)/k],MCaptureKthrow e1 to e2K;throw [] to e2e1,MThrowArgK;throw [] to e2vK;throw v to []e2,MThrowContK;throw v to []cont(K)Kv.MRestore The last line visibly discards K, including the two throw frames, and restores K. A continuation is persistent: throwing to it does not consume the stored stack.

This is the typed core of the familiar call/cc idiom. The term letcc k in e captures the current continuation, binds it to k, and evaluates e. In particular, the usual typed shape, Peirce’s law, is definable: λf:(AB)A.letcc k:ContA inf(λa:A.throw a to k):((AB)A)A. The inner throw is assigned result type B. The hypothetical dependent callcck of the final section is a different, explicitly call-by-name construct.

A nonlocal trace

Put K0:=;add1[],K1:=K0;add2[]. Call by value evaluates p:=add1(letcc k:ContNat in add2(throw 40 to k)) as follows. The initial application first reaches pMAppFun;[]e2add1MReturn;[]e2add1MAppArgK0e2, where e2=letcc k inadd2(throw 40 to k). Rule M-Capture substitutes the whole outer addition stack: K0e2MCaptureK0add2(throw 40 to cont(K0))MAppFunK0;[]e3add2MReturnK0;[]e3add2MAppArgK1e3MThrowArgK1;throw [] tocont(K0)40MReturnK1;throw [] tocont(K0)40MThrowContK1;throw 40 to []cont(K0)MReturnK1;throw 40 to []cont(K0)MRestoreK040MAdd41, where e3=throw 40 tocont(K0). The result is 41, not 43. Reinstating K0 erases the pending add2 frame. An exception can produce the same early return; the difference appears when cont(K0) is stored and invoked again.

Typing frames and states

Fix an ambient answer type R for one run. The runtime judgment ΓRe:A has all the surface rules and the internal continuation rule displayed below. On source terms it agrees with Γe:A. The frame judgment F:AB is defined so that plugging a value v:A into F produces the next computation of type B. Its term premises use the fixed ambient R: frametyping data and judgment[]e2Re2:A,[]e2:(AB)Bv[]Rv:AB,v[]:AB[],e2Re2:B,[],e2:AA×Bv,[]Rv:A,v,[]:BA×Bπi[]πi[]:A1×A2Aiinl[]inl[]:AA+Binr[]inr[]:BA+BabortB([])abortB([]):0Bcase [] of BRe1:D [x:A], Re2:D [y:B],case [] of B:A+BDthrow [] to e2Re2:ContA,throw [] to e2:ABthrow v to []Rv:A,throw v to []:ContAB. The bracketed notation in the case row means the branch has the displayed single free variable and becomes closed when that variable is replaced. The result B in the two throw rows is arbitrary, exactly as in T-Throw.

Stacks compose by

:RR
K-Empty
K:BRF:AB
K;F:AR
K-Push

The internal value rule and state judgments are

K:AR
Rcont(K):ContA
T-Cont
K:ARRe:A
RKe
S-Eval
K:ARRv:A
RKv
S-Ret

The subscript is essential: it prevents a continuation captured in a run returning R from being installed in a run returning an unrelated type. Only closed runs are needed here. An open machine can instead thread one term context through every frame while retaining the same ambient answer.

Exercise 35.1

★☆☆ Start from the state Lthrow 5 tocont(K0), where L:NatNat. Write every machine transition. Repeat from a different stack L:NatNat, using the same continuation value, and identify the transition which proves that continuations are persistent rather than one-shot.

Lemma 35.2 — Closed value substitution and canonical continuations

If Γ,x:ARe:B and ΓRv:A, then ΓRe[v/x]:B. For a closed value v, canonical forms are:

  1. if Rv:AB, then v=λx:A.e, or A=B=Nat and v=addm for some m;

  2. if Rv:A×B, then v=v1,v2;

  3. if Rv:A+B, then v=inlv0 or v=inrv0;

  4. there is no v with Rv:0;

  5. if Rv:ContA, then v=cont(K) for some K:AR.

Proof of Lemma 35.2 — Closed value substitution and canonical continuations

Proof. Substitution is induction on typing. In the T-Letcc case, rename its bound continuation away from x and the free variables of v; apply the induction hypothesis to the body and rebuild T-Letcc. In T-Throw, apply the two induction hypotheses to the value and continuation premises; the arbitrary result type is unchanged. The pure binder case is the same alpha-renaming argument for lambda abstraction, and products, sums, and elimination rules follow premise by premise.

For canonical forms, inspect the value grammar and invert the introduction rule for each form. Lambdas and the primitive addm are the only arrow values; pairs and the two injections are the only product and sum values; no introduction rule concludes 0; and the only continuation form is cont(K), whose T-Cont premise gives the required stack typing. The ground forms have base types and cannot enter the other cases. ◻

Theorem 35.3 — Machine preservation

For every answer type R, if Rs and ss, then Rs.

Proof of Theorem 35.3 — Machine preservation

Proof. The proof is by cases on the transition. Pushing a frame factors a stack typing through K-Push; returning to it composes the type promised by the frame with the rest of the stack. Application beta uses substitution. Pair, projection, injection, case, empty elimination, and arithmetic transitions use the corresponding frame row and, for case, substitution into the selected branch. Empty elimination has only its push case. These cases account for every pure transition displayed above.

For capture, inversion gives K:AR and k:ContAe:A. Rule T-Cont types cont(K):ContA; substitution gives the reduct type A, so S-Eval restores state type R.

For the first throw transition, inversion of T-Throw gives e1:A, e2:ContA, and the arbitrary source result B. The frame throw [] to e2:AB extends the stack. Returning the first value changes to the second throw frame, whose typing is ContAB. In the final control transition, canonical forms gives v2=cont(K) with K:AR; the stored value has type A. Hence Kv has the original state type R, even though the current stack was discarded. ◻

Theorem 35.4 — Machine progress

For every answer type R, if Rs, then exactly one of the following holds:

  1. s=v for a value with Rv:R;

  2. there is a unique state s with ss.

Proof of Theorem 35.4 — Machine progress

Proof. An evaluation state containing a value takes the direction-change rule. Every nonvalue has a unique outer constructor and therefore takes its unique displayed push, capture, or throw transition.

For a return state, an empty stack is final. Otherwise inspect its unique top frame. The first application, pair, injection, case, and throw frames advance to their second phase. The second application frame contains a function value, hence a lambda or the appropriate primitive by canonical forms. Projection receives a pair; case receives an injection; the second throw frame receives cont(K) by lemma 35.2. Thus each has exactly one transition. An abort frame would have to receive a value of type 0, which canonical forms excludes. The frame grammar has no remaining case. ◻

Corollary 35.5 — Safety

If e:A, then every finite run from e, typed with ambient answer A, remains well typed, and an irreducible reachable state is v with Av:A.

Proof of Corollary 35.5 — Safety

Proof. Induct on the run using preservation. Progress classifies the endpoint. ◻

Exercise 35.2

★★☆ Reconstruct the three throw cases of theorem 35.3. Write the type of each intermediate stack, including the arbitrary result type of the discarded throw site, and show where canonical forms is required.

Compiling control into pure functions

The stack judgment K:AR is represented in the target by a function AR. This is continuation-passing style (CPS): each computation receives an extra function describing what to do with its result, and calls that function instead of returning directly. The target is the pure simply typed calculus with the same base types, products, sums, and empty type 0, but without Cont, letcc, or throw. Its compatible βδ-reduction is strongly normalizing.

Proposition 35.6 — Normalization of the CPS target

Every well-typed target term is strongly normalizing for compatible βδ-reduction.

Proof of Proposition 35.6 — Normalization of the CPS target

Proof. The reducibility proof of theorem 2.43 applies unchanged to products, sums, unit, empty type, and arrows. Interpret the new base type Nat by the strongly normalizing terms of that type. Numerals belong to this candidate. To add addm, the only new candidate obligation says that addmt is strongly normalizing whenever t is. Compatible reduction of a finite term is finitely branching, with successors canonically enumerated by the finitely many redex positions and the finite rule list. Thus no choice function is used. The elementary finitely-branching form of König’s lemma says that an unbounded-height reduction tree would have an infinite branch; strong normalization excludes that branch, so the tree from t has a maximum branch length. Induction on that height handles steps inside t; when t is the numeral n, the sole new root step is addmnβδm+n, whose reduct is a numeral. Thus the fundamental reducibility lemma, and hence strong normalization, extends to the target used here. ◻

Definition 35.7 — Call-by-value CPS types

The value translation Av and computation translation Ac are 0v=0,1v=1,Natv=Nat,(A×B)v=Av×Bv,(A+B)v=Av+Bv,(AB)v=AvBc,(ContA)v=Av0,Ac=(Av0)0. For Γ=x1:A1,,xn:An, put Γv=x1:A1v,,xn:Anv. The fixed target answer 0 records that a CPS computation returns only by calling its supplied continuation.

With 0 read as falsehood, the computation type is the explicit double-negation translation Ac=¬¬Av. This is a call-by-value CPS translation rather than Kolmogorov’s formula translation: sums, products, and arrows are translated first as value types and the outer double negation is added to a computation. The theorem proved here is typed source-to-target simulation. No converse translation, reflection of provability, or completeness theorem for a separately presented classical proof system is claimed.

The translation separates source values from computations. On values it is homomorphic except at functions and reified stacks: xv=x,()v=(),nv=n,v1,v2v=v1v,v2v,(inlv)v=inlvv,(inrv)v=inrvv,(λx:A.e)v=λx:Av.ec. In chapter 22, a superscript v named the call-by-value translation into CBPV. Here v and c name the value and computation parts of this CPS translation. For every source value, the computation clause is uniformly vc=λk.kvv. The composite clauses below apply only when their whole subject is not a value. This disjointness matches the value-first side conditions of M-PairL, M-Inl, and M-Inr, so the translation is a function on syntax rather than two overlapping equations. The arithmetic function has the exact value translation addmv=λn:Nat.λk:Nat0.k(addmn). A stack value is translated below, because its function depends on the final answer continuation of the whole run.

Definition 35.8 — CPS translation of terms

The translation ec:Ac is defined by vc=λk.kvv(v a source value),(e1e2)c=λk.e1c(λf.e2c(λa.fak)),e1,e2c=λk.e1c(λa.e2c(λb.ka,b))(e1,e2 not a value),(πie)c=λk.ec(λp.k(πip)),(inle)c=λk.ec(λa.k(inla))(e not a value),(inre)c=λk.ec(λb.k(inrb))(e not a value). For a case with branches x.e1 and y.e2, (case e of {inlxe1;inrye2})c=λk.ec(λs.case s of{inlxe1ck;inrye2ck}). Empty elimination has the clause (abortA(e))c=λk:Av0.ec(λz:0.z). The outer continuation is unreachable; the target empty eliminator is the identity continuation on 0. Finally, the two control clauses are (letcc k:ContA in e)c=λκ:Av0. (ec[κ/k])κ,(throw e1 to e2)c=λκ. e1c(λa.e2c(λw.wa)). The second clause deliberately ignores κ. It nevertheless evaluates e1 before e2, matching the source stack machine.

The machine example has a short CPS image. For fresh h:Nat0, set q:=λa.add1vah. Unfolding the application and capture clauses, then contracting administrative beta redexes, gives pchβδ((add2(throw 40 to k))c[q/k])qβδq40βδadd1v40hβδh41. The continuation supplied while translating add2 is ignored by the translated throw, exactly as the machine discarded the pending add2 frame. Thus the target calculation displays the same 41, rather than 43, as the source trace.

Definition 35.9 — A stack as a target function

Fix a source result type R and a fresh target variable h:Rv0. Extend the translations to runtime syntax by vhv:=vv,ehc:=ec on surface values and terms, recursively using the same structural clauses, and put cont(K)hv:=Khk,cont(K)hc:=λw.wKhk. Thus the runtime extension, unlike the surface translation, is indexed by the final continuation. The translation Khk:Av0 of a stack K:AR is defined by hk=h,(K;[]e)hk=λf.ehc(λa.faKhk),(K;v[])hk=λa.vhvaKhk,(K;[],e)hk=λa.ehc(λb.Khka,b),(K;v,[])hk=λb.Khkvhv,b,(K;πi[])hk=λp.Khk(πip),(K;inl[])hk=λa.Khk(inla),(K;inr[])hk=λb.Khk(inrb). For a case frame with branches x.e1 and y.e2, (K;case [] of C)hk=λs.case s of{inlx(e1)hcKhk;inry(e2)hcKhk}. The abort frame discards its unreachable surrounding stack: (K;abortA([]))hk=λz:0.z. The two throw frames forget the continuation outside the throw site: (K;throw [] to e)hk=λa.ehc(λw.wa),(K;throw v to [])hk=λw.wvhv. The translations of machine states are Keh:=ehcKhk,Kvh:=Khkvhv.

Lemma 35.10 — CPS substitution

For runtime syntax at a fixed final continuation h, (e[v/x])hc=ehc[vhv/x]. For surface syntax the subscripts erase, giving the ordinary source-value substitution equation.

Proof of Lemma 35.10 — CPS substitution

Proof. Induct on e, alpha-renaming lambda and letcc binders before descending. Every structural clause distributes substitution to its subterms. In the control clauses, a continuation variable is an ordinary target variable of function type Av0. The internal continuation case is the same target substitution after replacing the stack by its function. ◻

The fresh h is necessary. There is no closed target function Rv0 in general, so an empty source stack cannot be represented by a closed target continuation. Strong normalization holds for well-typed open target terms as well, so keeping h free costs nothing.

Theorem 35.11 — CPS type preservation

Fix an ambient source answer type R and a fresh h:Rv0. The following judgments hold.

  1. If ΓRv:A, then Γv,h:Rv0vhv:Av.

  2. If ΓRe:A, then Γv,h:Rv0ehc:Ac.

  3. If K:AR, then h:Rv0Khk:Av0. Consequently every well-typed state of result type R translates to a target term of type 0.

Proof of Theorem 35.11 — CPS type preservation

Proof. The three claims are simultaneous induction on runtime value, term, frame, and stack typing. A variable or base value is passed to a continuation of its value type. The internal T-Cont case uses the stack induction hypothesis to type Khk:Av0. For a lambda, the induction hypothesis gives ehc:Bc under x:Av; hence the value inside the outer continuation has type AvBc=(AB)v. For application, the successive binders in λk.e1c(λf.e2c(λa.fak)) have types Bv0, Av0, and (AvBc)0, in that order from the inside out. Thus the whole term has type Bc.

Pairing sequences values of types Av and Bv before passing their pair. Projection passes an Av×Bv value to a target projection. Injection and case use the corresponding target sum rules; in the two case branches the corresponding induction hypothesis is applied under the same assumption k:Cv0. For empty elimination, apply the premise translation to λz:0.z; its result is 0, so abstracting the unused continuation gives Ac. Variables, abstractions, applications, pairs, projections, injections, case, and empty elimination exhaust the pure source constructors.

For T-Letcc, the target variable κ:Av0=(ContA)v has exactly the type assigned to the translated source variable. The body induction hypothesis types ec[κ/k]:(Av0)0; applying it to κ gives 0, and abstracting over κ gives Ac. For T-Throw, write the two source premises as e1:A and e2:ContA. The innermost target application has c:Av0,a:Av,ca:0. It is a continuation accepted by e2c; that result is a continuation accepted by e1c. The unused outer binder may have type Bv0, so the result is Bc for the arbitrary source type B.

For the stack cases, each clause of definition 35.9 has as domain the translated input of its top frame and calls Khk only with the frame’s translated output. The abort frame and two throw clauses have result 0 without calling Khk, exactly as their arbitrary frame result requires. Applying the term or value translation to the resulting function proves the state claim. ◻

Theorem 35.12 — Positive-step machine simulation

For every answer type R, if Rs and ss, then, for fresh h:Rv0, shβδ+sh. Every source step is represented by at least one target step.

Proof of Theorem 35.12 — Positive-step machine simulation

Proof. Expand the term and top-frame translations. The direction change for a value is (λk.kvhv)KhkβKhkvhv. The application push is (e1e2)hcKhkβ(e1)hc(λf.(e2)hc(λa.faKhk)), which is exactly the translation of K;[]e2e1. Returning a function contracts the outer binder of this displayed continuation; returning its argument contracts the next binder and then target beta. By lemma 35.10, the endpoint is (e[v/x])hcKhk, the translation of the source beta state.

The remaining pure pushes are not implicit. Expanding their outer CPS binders gives Ke1,e2hβK;[],e2e1h,KπiehβK;πi[]eh,KinlehβK;inl[]eh,KinrehβK;inr[]eh,Kcase e of ChβK;case [] of Ceh,KabortA(e)hβK;abortA([])eh. Returning the first pair component contracts the a-binder and produces the second pair frame; returning the second contracts the b-binder and produces Khkv1v,v2v. Projection return contracts the frame binder and the target projection. Injection return contracts its payload binder. Either case return contracts the scrutinee binder, takes the matching target sum contraction, and uses CPS substitution in the selected branch. No abort return is typable.

For the primitive, put sadd:=K;addm[]n. Also abbreviate the translated frame by Lh:=λa.addmvaKhk. The generic application-frame clause and the displayed translation of addm give saddh=frametranslationLhnβaddmvnKhkβ+Khk(addmn)βδKhk(m+n)=statetranslationK(m+n)h. Thus the primitive source step is represented by beta steps followed by the target primitive’s δ-step. Every pure transition family is now covered.

Capture gives the characteristic equation (letcc k in e)hcKhkβehc[Khk/k]Khk=(e[cont(K)/k])hcKhk, where the equality is the internal case of CPS substitution. The initial throw push is (throw e1 to e2)hcKhkβ(e1)hc(λa.(e2)hc(λw.wa)), which is precisely the first throw-frame translation. The next return contracts a, giving the second evaluation state. If its continuation value is cont(K), the last return reduces as (λw.wvhv)KhkβKhkvhv, the translation of Kv. This enumerates every rule of the machine. ◻

The requirement βδ+, rather than βδ, is load bearing. A translation which represented some source steps by no target step would not by itself transfer termination of the deterministic machine.

Theorem 35.13 — Termination of the λ _ K machine

For every answer type R, every closed state s with Rs has no infinite λK transition sequence.

Proof of Theorem 35.13 — Termination of the λ _ K machine

Proof. Suppose s0s1 were an infinite well-typed run. Preservation keeps one result type R. Choose fresh h:Rv0. Positive-step simulation concatenates to an infinite βδ-reduction s0hβδ+s1hβδ+. CPS typing gives a well-typed pure target term. This contradicts proposition 35.6. ◻

Corollary 35.14 — Relative consistency of the continuation calculus

Relative to strong normalization of the pure CPS target, there is no closed λK term of type 0.

Proof of Corollary 35.14 — Relative consistency of the continuation calculus

Proof. If e:0, strong normalization and progress take e to a final state v with v:0. The value grammar has no value of empty type, a contradiction. ◻

Exercise 35.3

★★★ Starting only from the two CPS clauses for letcc and throw, derive their target types. Then reproduce the capture case and the three throw cases of theorem 35.12, marking the target step which makes each source simulation positive.

Classical proofs execute by changing the future

Under propositions as types, read 0 as falsehood , A+B as disjunction AB, and define ¬A:=A. The relevant intuitionistic natural-deduction rules are

Γ,A
Γ¬A
I
Γ¬AΓA
Γ
E
ΓA
ΓAB
I_1
ΓB
ΓAB
I_2

Disjunction elimination is ΓABΓ,ACΓ,BCΓC(E). Empty elimination is ΓΓC(E), and implication introduction and elimination are the inherited lambda and application rules. These rules do not include Γ¬¬AΓA(DNE),nor do they deriveΓA¬A in general. A two-world Kripke frame w0w1, with P forced only at w1 and forced nowhere, refutes P¬P at w0: neither P nor P is forced there. Kripke soundness for exactly the displayed propositional rules is a rule induction (one case per rule): assumptions are monotone, implication introduction quantifies over extensions, implication elimination and the disjunction rules preserve forcing, and the empty-elimination case is vacuous. Soundness therefore rules out an intuitionistic derivation.

With the displayed E, the two classical extensions are equivalent. From n:¬¬A, case-analyze A¬A; the left branch returns A, and the right branch derives as nq and eliminates it. Conversely, from n:¬(A¬A), the function λa.n(inla) proves ¬A, so n(inr(λa.n(inla))) proves ; DNE at A¬A completes the other direction.

Now read ContA as an operational program representation of ¬A, not as its definitional equal. It is represented in CPS by Av0, and throwing an A-value to such a continuation produces no local result. The control typing rules then give double-negation elimination directly.

Proposition 35.15 — Continuation-form double negation

For every type A, the term dneA:=λn:Cont(ContA).letcc k:ContA in throw k to n has type Cont(ContA)A. If n=cont(N), its characteristic machine trace under a stack K:AR is Kletcc k inthrow k to nMCaptureKthrow cont(K) tocont(N)MThrowArg,MReturn,MThrowCont,MReturn,MRestoreNcont(K). Thus n, which claims to refute every refutation of A, receives the current A-continuation and must do something with it.

Proof of Proposition 35.15 — Continuation-form double negation

Proof. The body has the complete derivation n:Cont(ContA),k:ContAk:ContAn:Cont(ContA),k:ContAn:Cont(ContA)A typen:Cont(ContA),k:ContAthrow k to n:ATThrown:Cont(ContA)letcc k:ContA inthrow k to n:ATLetcc. Lambda introduction derives Cont(ContA)A. For the trace, capture substitutes cont(K); the two throw frames evaluate that value and cont(N), after which the last rule of (35.1) discards K and restores N. ◻

Excluded middle uses the same mechanism twice. It first returns a continuation as evidence for the right summand. The only way to refute that evidence is to supply an A-value; doing so re-enters the original caller with the value in the left summand.

Proposition 35.16 — Continuation-form excluded middle with a change-of-mind trace

For every type A, define lemA:=letcc k:Cont(A+ContA) ininl(letcc q:ContA inthrow (inrq) to k). Then lemA:A+ContA. For every stack K:A+ContAR, put K=K;inl[]. Its first return is KlemAMCapture,MInl,MCapture,MThrowArg,MReturn,MThrowCont,MReturn,MRestoreKinr(cont(K)). If any later stack L throws a:A to this returned continuation, then Lthrow a tocont(K)MThrowArg,MReturn,MThrowCont,MReturn,MRestoreKaMInlRetKinla.

Proof of Proposition 35.16 — Continuation-form excluded middle with a change-of-mind trace

Proof. Put B:=A+ContA and Γ0:=k:ContB,q:ContA. The complete derivation of the body is Γ0q:ContAΓ0inrq:BInrΓ0k:ContBA typeΓ0throw (inrq) to k:ATThrowk:ContBletcc q:ContA inthrow (inrq) to k:ATLetcck:ContBinl(letcc q:ContA inthrow (inrq) to k):BInllemA:BTLetcc. The two variable premises are instances of Var; the last premise of T-Throw records its arbitrary result type A.

Operationally, the outer capture substitutes cont(K). The injection frame is pushed before the inner capture, so that capture yields q=cont(K). Throwing its right injection to cont(K) discards K and gives the first displayed return. The second calculation is the throw rule followed by the injection-frame return. Both traces include the same caller stack K; this is why the caller repeats its case analysis and can take the other branch. ◻

Corollary 35.17 — Double-negation elimination and excluded middle

For every type A, λK has closed terms of the actual propositions ((A0)0)A,A+(A0).

Proof of Corollary 35.17 — Double-negation elimination and excluded middle

Proof. For double-negation elimination use λn:(A0)0.letcc k:ContA in abortA(n(λa:A.throw a to k)). The inner lambda has type A0 because its throw is assigned result type 0; hence n produces 0, which abortA eliminates. For excluded middle, case-analyze lemA. Map inla to inla, and map inrq, where q:ContA, to inr(λa:A.throw a to q). The latter lambda again has type A0. ◻

These propositions prove classical formulas by typed programs. They do not make double-negation elimination or excluded middle a definitional equality of the pure calculus. Their machine reductions capture and reinstate stacks; their CPS images are pure functions with additional beta reductions. Consistency follows from that translation, not from erasing the operational difference between classical and intuitionistic proof terms.

Exercise 35.4

★★☆ Translate lemA to CPS and beta-reduce it to a term of type ((Av+(Av0))0)0. Identify the target subterm which first calls the supplied continuation with a right injection and the occurrence which later calls it with a left injection.

Delimiters, dependent projection, and let-polymorphism

The preceding continuations contain the whole stack. A reset instead marks a boundary, and shift0 or control0 captures only the evaluation context inside the nearest boundary. The four translations below are typable only under a specific polymorphic type-and-effect signature, and that signature is the content of this section. No theorem below identifies these calculi with λK.

The section revisits deep resumption, handler clauses, and effect annotations in a deliberately different, unlabeled ordered-row signature. No theorem identifies its do form with the labeled operations of chapter 22, chapter 25. In the first of those chapters, an operation is opV(x.M), a handler is handle M with H, and the annotation Eout is a finite set. In chapter 25, the corresponding forms are perform v, handle e with H, and the unordered row ϵ. Here they are do v, handle e{x,r.eh; y.er}, and the ordered row ρ. This last vocabulary is not inherited from either earlier chapter; it is the common core in which the four correspondences below are stated.

A delimited correspondence must record two facts that the whole-stack machine does not: which row entry marks the nearest delimiter, and whether invoking a captured continuation reinstalls that delimiter. The common core makes the row explicit before either control operator is introduced.

The common ordered-row core

Definition 35.18 — The Pir'og–Polesiuk–Sieczkowski (PPS) common core

Kinds, types, and rows are κ::=TER,τ::=αTτρτα::κ.τ,ε::=αEεext,ρ::=αRιερ. The rule-name prefix K- denotes kind formation in this comparison signature; it is unrelated to the machine-stack metavariable K used in the first part of the chapter. The subscripts on variables are metanotational kind tags, not source punctuation. A metavariable σ below ranges over any expression of the kind demanded by its judgment. Thus the raw grammar never identifies a function type with a row; εext stands for one of the extension-specific effects declared below. Here ι is the empty row. A row is ordered: the head effect is the one encountered by the nearest matching delimiter. The common terms and evaluation contexts are v::=xλx.e,e::=vee[e],E::=[]EevE[E]. Three similar brackets have different syntactic jobs. The term [e] is a lift, [] is a one-hole evaluation context, and E[e] denotes plugging e into that hole. We retain these source-calculus brackets so the correspondence clauses can be compared directly with their published forms. The following rules generate every well-formed type, effect, and row:

α::κΔ
Δα::κ
K-Var
Δτ1::TΔρ::RΔτ2::T
Δτ1ρτ2::T
K-Arr
Δ,α::κτ::T
Δα::κ.τ::T
K-All

Rule K-Var handles all three sorted variable cases uniformly. Rows are formed by

Δι::R
K-Nil
Δε::EΔρ::R
Δερ::R
K-Cons

Each extension below contributes its own single-effect formation rule. When a judgment writes a single effect ε to the right of /, it abbreviates the row ει. This is the omitted trailing-ι convention used whenever a rule displays a single effect after /.

The judgment Δ;Γe:τ/ρ records both result type and evaluation effect. Variables and lambdas are pure; application aligns the latent and evaluation rows:

x:τΓ
Δ;Γx:τ/ι
P-Var
Δ;Γ,x:τ1e:τ2/ρ
Δ;Γλx.e:τ1ρτ2/ι
P-Lam
Δ;Γe1:τ1ρτ2/ρΔ;Γe2:τ1/ρ
Δ;Γe1e2:τ2/ρ
P-App

Write <: for the kind-indexed preorder, read as subtyping at T and subrowing at R. It is the PPS kinded preorder, not the earlier term-type subtyping judgment that shares its printed glyph. It is generated exactly by

Δσ<:σ
Sub-Refl
Δτ21<:τ11Δρ1<:ρ2Δτ12<:τ22
Δ(τ11ρ1τ12)<:(τ21ρ2τ22)
Sub-Arr
Δ,α::κτ1<:τ2
Δα::κ.τ1<:α::κ.τ2
Sub-All

The row clauses are

Δρ::R
Δι<:ρ
Sub-Nil
Δρ1<:ρ2
Δερ1<:ερ2
Sub-Cons

Generalization, instantiation, subtyping, and lift are explicit derivation steps:

Δ,α::κ;Γe:τ/ιαFV(Γ)
Δ;Γe:α::κ.τ/ι
P-Gen
Δσ::κΔ;Γe:α::κ.τ/ρ
Δ;Γe:τ[σ/α]/ρ
P-Inst
Δτ1<:τ2Δρ1<:ρ2Δ;Γe:τ1/ρ1
Δ;Γe:τ2/ρ2
P-Sub

Transitivity is not a primitive rule. Every use of P-Sub invokes one displayed subtype derivation; no hidden transitive chain is needed. The remaining core rule is

Δε::EΔ;Γe:τ/ρ
Δ;Γ[e]:τ/ερ
P-Lift

The lift is a type-and-control mask. If e:τ/ρ, it inserts an arbitrary well-kinded head effect, yielding [e]:τ/ερ, while [v]0v erases an inert mask. Operationally, a context [E] has freeness index one rather than zero, so a capture beneath that lift cannot jump directly to the surrounding delimiter. The translations below preserve each explicit lift; delimiter clauses discharge the effect head introduced by the corresponding source delimiter rule. In this core e0e again denotes a root contraction. The complete core dynamics and contextual closure are (λx.e)v0e[v/x],[v]0v,e0eE[e]E[e]. Freeness is generated by 0-free([]),n-free(E)n-free(Ee),n-free(E)n-free(vE),n-free(E)(n+1)-free([E]). Capture rules below require a 0-free context, so they cannot cross an unmasked nearer delimiter. Read the index as the balance of unmatched lifts on the path from the hole to the root: application frames preserve it, a lift raises it, and each delimiter rule below discharges one. Hence a 0-free context is exactly balanced for the delimiter whose root rule is being applied.

The notation changes at this boundary. In chapter 4, rows are unique-label and unordered. In chapter 25, ε is a duplicate-label unordered row and μ a row variable. Here ε is one effect, ρ is a duplicate-permitting ordered row, and μ below binds a recursive effect. The kind context Δ and term context Γ are not the store typing Σ used in the state fragments of earlier chapters. These distinctions, rather than a shared row notation, are used by the translations. The arrow in Δ0.τ1τ2 is the single-effect signature separator, corresponding to the earlier PR. It is unrelated to the stack/frame typing judgment F:AB used in the first two sections.

Deep handlers and shift0

Definition 35.19 — Deep handlers

A deep single effect has form Δ0.τ1τ2. It may bind variables of all three kinds. Terms add do v,handle e {x,r.eh; y.er}. Its formation rule, evaluation-context extension, and freeness rule are

Δ,Δ0τ1::TΔ,Δ0τ2::T
ΔΔ0.τ1τ2::E
K-DH
(n+1)-free(E)
n-free(handle E{x,r.eh; y.er})
Free-DH

Evaluation contexts add E::=handle E{x,r.eh; y.er}. Here Δδ::Δ0 means that δ has exactly the domain of Δ0 and Δδ(α)::Δ0(α) for every bound α. For a well-kinded substitution δ::Δ0, operation invocation and handling are typed by

Δ;Γv:δτ1/ιΔδ::Δ0ΔΔ0.τ1τ2::E
Δ;Γdo v:δτ2/(Δ0.τ1τ2)
DH-Do

and

Δ;Γe:τ/(Δ0.τ1τ2)ρΔ,Δ0;Γ,x:τ1,r:τ2ρτreh:τr/ρΔ;Γ,y:τer:τr/ρ
Δ;Γhandle e{x,r.eh; y.er}:τr/ρ
DH-Handle

If E is 0-free, its operation contraction is handle E[do v]{x,r.eh; y.er}0eh[v/x,(λz.handle E[z]{x,r.eh; y.er})/r]. The handler is placed around the captured continuation again; this is the meaning of deep. On a returned value, handle v{x,r.eh; y.er}0er[v/y].

Definition 35.20 — The parametric shift_0 calculus

A delimited-control effect has form Δ0.τ/ρ. Terms add shift0 k.e and a reset with return clause, written ex.er. Formation and freeness are

Δ,Δ0τ::TΔ,Δ0ρ::R
ΔΔ0.τ/ρ::E
K-S0
(n+1)-free(E)
n-free(Ex.er)
Free-S0

Evaluation contexts add E::=Ex.er. The exact typing rules are

Δ,Δ0;Γ,k:τρτe:τ/ρ0Δ,Δ0ρ0<:ρΔτ::TΔ(Δ0.τ/ρ)ρ0::R
Δ;Γshift0 k.e:τ/(Δ0.τ/ρ)ρ0
S0-Shift

and

Δδ::Δ0Δ;Γe:τ/(Δ0.τ/ρ)δρΔ;Γ,x:τer:δτ/δρ
Δ;Γex.er:δτ/δρ
S0-Reset

For 0-free E, E[shift0 k.e]x.er0e[(λz.E[z]x.er)/k], and vx.er0er[v/x]. The captured continuation contains the reset; invoking it is therefore deep-like.

For a concrete root calculation, temporarily add the pure arithmetic constants used in the stack machine and put E=add2[],e=k40,er=x. The S0-Shift contraction gives add1add2(shift0 k.k40)x.x0add1((λz.add2zx.x)40)43. The substituted continuation reinstalls the reset around precisely the inner add2[] context; the outer add1[] frame is not captured.

Definition 35.21 — Deep bridge translations

There are translations DH() from shift0 to deep handlers and DD() in the reverse direction. They are homomorphic on every construct except the following displayed term clauses: DH(shift0 k.e)=do(λk.DH(e)),DH(ex.er)=handle DH(e){f,r.fr; x.DH(er)},DD(do v)=shift0 k.λh.hDD(v)(λx.kxh),DD(handle e{x,r.eh; y.er})=DD(e)y.λh.DD(er)(λx.λr.DD(eh)). For the first single-effect clause, name T=DH(τ),R=DH(ρ),Ca=aRT,Qa=Δ0.(CaRT). Thus the prefix a::T. binds the target operation’s answer type, whereas Δ0 re-quantifies the source effect’s parameters inside its argument type. On single effects the clauses are exactly DH(Δ0.τ/ρ)=a::T.Δ0.((aDH(ρ)DH(τ))DH(ρ)DH(τ))a,DD(Δ0.τ1τ2)=α::T,β::R.(Δ0.DD(τ1)(DD(τ2)βα)βα)βα/β. Equivalently, the first right-hand side is a::T.Qaa. Here and below an undecorated arrow is pure. The four named operators are syntax-directed translations.

Lemma 35.22 — Deep-bridge type substitution

For X{DH,DD}, X(τ[σ/α])=X(τ)[X(σ)/α].

Proof of Lemma 35.22 — Deep-bridge type substitution

Proof. Induct on types and rows. At a quantified effect, alpha-rename every binder away from α and ftv(σ) before applying the induction hypothesis beneath the binder. ◻

Lemma 35.23 — Deep-bridge value substitution

For X{DH,DD}, X(e[v/x])=X(e)[X(v)/x].

Proof of Lemma 35.23 — Deep-bridge value substitution

Proof. Induct on terms. Alpha-rename lambda, shift, handler, and return-clause binders away from x and FV(v) before descent. For example, choose k,h,yFV(DD(v)){x}. The reverse operation clause gives DD((dow)[v/x])=shift0 k.λh.hDD(w[v/x])(λy.kyh), which is the induction hypothesis substituted into DD(dow). The remaining nonhomomorphic clauses commute with capture-avoiding substitution in the same way. ◻

Lemma 35.24 — Deep-bridge context compatibility

For X{DH,DD}, X(E[e])=X(E)[X(e)]. Moreover, n-free(E) implies n-free(X(E)).

Proof of Lemma 35.24 — Deep-bridge context compatibility

Proof. Use simultaneous induction on the evaluation-context and freeness derivations. Application and lift use the common context constructors. A source delimiter becomes exactly one target delimiter, so its premise raises freeness from n to n+1 in both calculi. ◻

Lemma 35.25 — Deep-bridge translation algebra

The deep translations commute with type substitution, value substitution, and context plugging, and preserve delimiter freeness.

Proof of Lemma 35.25 — Deep-bridge translation algebra

Proof. Combine lemma 35.22, lemma 35.23, lemma 35.24. ◻

The tempting reverse operation clause DDbad(do v)=shift0 k.λh.hDD(v)k does not type-check. The captured k returns the handler-code function still expected outside the capture, whereas h requires a resumption that returns the handler result after receiving that code. Closing over h in λx.kxh rethreads the deep handler on every resumption.

Lemma 35.26 — Deep-bridge formation

The translations in definition 35.21 preserve kinding and subtyping in both directions.

Proof of Lemma 35.26 — Deep-bridge formation

Proof. Translate kinding and subtyping simultaneously; this fixes the scope of Δ0 in the handler-code type. Use lemma 35.25 at every type-substitution boundary.

Formation. Induct simultaneously on kinding and subtyping. Variable, arrow, universal, empty-row, and row-cons formation are homomorphic, as are reflexive, arrow, universal, empty-row, and row-cons subtyping. The homomorphic cases follow their corresponding source constructors. For the translated-effect instance of Sub-Cons, the induction hypothesis on ρ1<:ρ2 gives DH(ρ1)<:DH(ρ2); one target Sub-Cons step then derives DH(ε)DH(ρ1)<:DH(ε)DH(ρ2). The DD direction is identical after replacing each translation symbol.

The two nonhomomorphic effect-formation cases do not follow from those homomorphic clauses. For the forward K-S0-to-K-DH case put T=DH(τ),R=DH(ρ),Ca=aRT, Qa=Δ0.(CaRT),η=a::T.Qaa. The source premises translate to Δ,Δ0T::T and Δ,Δ0R::R. Under fresh a::T, write Ξ=Δ,a::T,Δ0. Weakening derives ΞT::T and ΞR::R. The complete target derivation is Ξa::TΞR::RΞT::TΞCa::TKArr. ΞCa::TΞR::RΞT::TΞCaRT::TKArr. Δ,a::T,Δ0CaRT::TΔ,a::TQa::TKAllΔ0, Δ,a::TQa::TΔ,a::Ta::TΔη::EKDH. Δη::EΔι::RΔηι::RKCons. Here KAllΔ0 denotes one displayed K-All step for every binder of Δ0, retaining each declared kind T, E, or R. Thus the universal really scopes the entire function CaRT, and the result is both a deep effect and, after row cons, a row.

For K-DH-to-K-S0, put Da,b=τ2ba,Ga,b=Da,bba, Ha,b=Δ0.(τ1ιGa,b),θ=a::T,b::R.(Ha,bba)/b. The translated K-DH premises give Δ,Δ0τi::T for i=1,2. Under fresh a::T,b::R, write Ω0=Δ,a::T,b::R and Ω=Ω0,Δ0. Weakening derives Ωτi::T for i=1,2. The three K-Arr derivations are Ωτ2::TΩb::RΩa::TΩDa,b::TKArr. ΩDa,b::TΩb::RΩa::TΩGa,b::TKArr. Ωτ1::TΩι::RΩGa,b::TΩτ1ιGa,b::TKArr. The remaining formation tree is Ωτ1ιGa,b::TΩ0Ha,b::TKAllΔ0, Ω0Ha,b::TΩ0b::RΩ0a::TΩ0Ha,bba::TKArr, Ω0Ha,bba::TΩ0b::RΔθ::EKS0. Consequently K-Cons also gives Δθι::R. This displays both the polymorphic handler-code kind and the effect/row result required by the reverse translation. ◻

Lemma 35.27 — shift_0-to-deep type preservation

For every well-formed kinding context and term context, and every well-formed type/effect pair, Δ;Γe:τ/ρΔ;DH(Γ)DH(e):DH(τ)/DH(ρ).

Proof of Lemma 35.27 — shift_0-to-deep type preservation

Proof. Induct on the source typing derivation. Rule P-Gen retains the empty row; P-Inst uses type substitution from lemma 35.25; and P-Sub uses lemma 35.26. Variable, lambda, application, and lift rebuild their corresponding target rules. It remains to check the two nonhomomorphic cases.

For a source effect Δ0.τ/ρ, put T=DH(τ),R=DH(ρ),CX=XRT, QX=Δ0.(CXRT),η=a::T.Qaa.

For the control-to-handler implication, the two nonhomomorphic typing cases use U=DH(τ),R0=DH(ρ0). For S0-Shift, the induction hypothesis and the translated premise give Δ,Δ0;Γ,k:CUDH(e):T/R0,R0<:R. Alpha-rename every binder of Δ0 away from FV(Γ); hence each use of P-Gen satisfies its freshness side condition. Apply P-Sub to the evaluation row, then P-Lam, then P-Gen once for each binder in Δ0: Γ,k:CUDH(e):T/R,PSubΓλk.DH(e):CURT/ι,PLamΓλk.DH(e):QU/ι.PGen Rule DH-Do uses the explicit instantiation aU and derives do(λk.DH(e)):U/η. Finally, Sub-Cons lifts ι<:R0 beneath η. Rule P-Sub then gives U/ηR0, the translated conclusion.

For S0-Reset, let δ::Δ0 be its displayed instantiation. The induction hypotheses type the handled computation at U/ηδR and the return clause at δT/δR. In the operation clause of DH-Handle, f:Qa,r:aδRδT. Rule P-Inst specializes every binder of f by δ, so f:(aδRδT)δRδT. Lift the two pure variable judgments to δR by P-Sub and apply P-App; this derives fr:δT/δR. Thus DH-Handle has all three premises and returns δT/δR.

These calculations establish the implication. ◻

Lemma 35.28 — Deep-to- shift_0 type preservation

For every well-formed Δ,Γ,τ,ρ, Δ;Γe:τ/ρΔ;DD(Γ)DD(e):DD(τ)/DD(ρ).

Proof of Lemma 35.28 — Deep-to- shift_0 type preservation

Proof. Induct on the source typing derivation. Common rules are homomorphic, with substitution and subtyping supplied by lemma 35.25, lemma 35.26. It remains to check the operation and handler rules.

For a source effect Δ0.τ1τ2 with trailing row ι, put Da,b=τ2ba,Ga,b=Da,bba, Ha,b=Δ0.(τ1ιGa,b),θ=a::T,b::R.(Ha,bba)/b. The undecorated arrow in definition 35.21 is the ι-arrow in this definition of Ha,b. In a translated DH-Do premise, let δ0::Δ0 be the operation’s instantiation. Under fresh a,b, P-Inst gives h:δ0τ1(δ0τ2ba)ba,k:δ0τ2b(Ha,bba). After lifting pure variables to row b, repeated P-App and P-Lam derive, in order, kxh:a/b,λx.kxh:δ0τ2ba/ι,hDD(v)(λx.kxh):a/b, and hence λh.hDD(v)(λx.kxh):Ha,bba/ι. Rule S0-Shift, with its binders instantiated by the fresh a,b, uses ι<:b and concludes δ0τ2/θι.

Finally suppose the source DH-Handle has result τr/ρ, and put Hr=Δ0.τ1(τ2ρτr)ρτr. The translated reset uses the explicit substitution [aτr,bρ]. Its computation premise has type τ/θρ, while x.λh.DD(er) has type Hrρτr/ρ; hence S0-Reset yields Hrρτr/ρ. Generalizing the translated operation clause over Δ0 gives λx.λr.DD(eh):Hr/ι. One final P-App, after pure-row widening, gives τr/ρ. This also explains why the captured resumption is λx.kxh, not merely k. ◻

Lemma 35.29 — Deep-bridge root simulation

The deep bridge preserves each source step by a nonempty target reduction:

  1. if ee in shift0, then DH(e)+DH(e) by ordinary evaluation-context reduction;

  2. if ee with deep handlers, then DD(e)i+DD(e), where i closes a target contraction under an arbitrary one-hole term context.

Proof of Lemma 35.29 — Deep-bridge root simulation

Proof. For semantic preservation, write D={f,r.fr; y.DH(er)},vc=λz.handle DH(E)[z]D. The translated shift0 root is the complete calculation DH(E[shift0 k.e]y.er)=handle DH(E)[do(λk.DH(e))]D(λk.DH(e))vcβDH(e)[vc/k]=DH(e[(λz.E[z]y.er)/k]), where the last equality is lemma 35.25. Every step is an ordinary evaluation step.

In the reverse direction put H=λx.λr.DD(eh),Ry=λh.DD(er),Q[z]=DD(E)[z]y.Ry. Then the translated deep-operation root is DD(handle E[do v]{x,r.eh; y.er})=DD(E)[shift0 k.λh.hDD(v)(λz.kzh)]y.RyH(λh.hDD(v)(λz.(λw.Q[w])zh))Hi+HDD(v)(λz.Q[z]H)i+DD(eh)[DD(v)/x,(λz.Q[z]H)/r]=DD(eh[v/x,(λz.handle E[z]{x,r.eh; y.er})/r]). The contraction of (λw.Q[w])z occurs beneath λz, so ordinary evaluation-context reduction is insufficient; this is exactly the use of arbitrary-context i. Handler/reset return roots contract directly and then use substitution. The context and freeness parts of lemma 35.25 lift these root calculations, proving the two stated positive closures. ◻

Theorem 35.30 — Typed deep/ shift_0 correspondence

For every well-formed Δ,Γ,τ,ρ, type-and-effect preservation has two directions. From shift0, Δ;Γe:τ/ρΔ;DH(Γ)DH(e):DH(τ)/DH(ρ). From deep handlers, Δ;Γe:τ/ρΔ;DD(Γ)DD(e):DD(τ)/DD(ρ). The two directions differ in the reduction relation the simulation needs:

  1. if ee in shift0, then DH(e)+DH(e) by ordinary evaluation-context reduction;

  2. if ee with deep handlers, then DD(e)i+DD(e) under arbitrary compatible closure.

Proof of Theorem 35.30 — Typed deep/ shift_0 correspondence

Proof. The two typing implications are lemma 35.27, lemma 35.28; the two positive simulations are lemma 35.29. ◻

A fully typed deep root.

Fix A::T and a:A, and put εs=.A/ι,εh=b::T.((bιA)ιA)b. The empty quantifier in εs disappears in εh. The complete source derivation for dA=shift0 k.kax.x is

A::T::
a:A,k:AιAk:(AιA)/ι
P-Var
a:A,k:AιAa:A/ι
P-Var
a:A,k:AιAka:A/ι
P-App
ι<:ιA::Tεsι::R
a:Ashift0 k.ka:A/εsι
S0-Shift
a:A,x:Ax:A/ι
P-Var
a:AdA:A/ι
S0-Reset

Put Dh={f,r.fr; x.x}. Its translation is hA=handle do(λk.ka)Dh. No typing premise is implicit: P-App followed by P-Lam gives a:Aλk.ka:(AιA)ιA/ι, and DH-Do, instantiated by bA, gives a:Ado(λk.ka):A/εh. For the handler’s operation premise, under fresh b::T, f:(bιA)ιA,r:bιAfr:A/ι by two P-Var leaves and P-App; its return premise is x:Ax:A/ι by P-Var. Rule DH-Handle therefore derives a:AhA:A/ι. The typed source root and its target simulation are dAcapture(λz.zx.x)aβax.xresetreturna,hAhandlercapture(λk.ka)(λz.handle zDh)β(λz.handle zDh)aβhandle aDhhandlerreturna.

Exercise 35.5

★☆☆ In the first effect translation of theorem 35.30, instantiate Δ0 by one type variable γ::T and take τ=γ, ρ=ι. Write the complete deep operation type. Explain why γ must scope the whole function from captured continuations to answers, rather than only the captured-continuation type.

Shallow handlers and control0

Deep resumption reinstalls its delimiter. Shallow resumption does not. The type of a resumed computation can therefore expose the same leading effect again, which forces recursion at the level of effect specifications.

Definition 35.31 — The shallow pair

A shallow-handler effect is ε=μα.Δ0.τ1τ2, with α::E available in τ1,τ2. Its formation rule is

Δ,α::E,Δ0τ1::TΔ,α::E,Δ0τ2::T
Δμα.Δ0.τ1τ2::E
K-SH

Terms and handler evaluation contexts are those of the deep calculus, with the same freeness rule (n+1)-free(E)n-free(handle E{x,r.eh; y.er}). The shallow contractions are handle E[do v]{x,r.eh; y.er}0eh[v/x,(λz.E[z])/r](0-free(E)),handle v{x,r.eh; y.er}0er[v/y]. Put δε=δ[αε]. The complete operation and handler rules are

ε=μα.Δ0.τ1τ2Δ;Γv:δετ1/ιΔδ::Δ0Δε::E
Δ;Γdo v:δετ2/ε
SH-Do

and

ε=μα.Δ0.τ1τ2Δ;Γe:τ/ερΔ,Δ0;Γ,x:τ1[ε/α],r:τ2[ε/α]ερτeh:τr/ρΔ;Γ,y:τer:τr/ρ
Δ;Γhandle e{x,r.eh; y.er}:τr/ρ
SH-Handle

The resumption omits the handler and may perform ε again.

A control0 effect is ε=μα.Δ0.τ1τ2/ρ. Its formation rule is

Δ,α::E,Δ0τ1::TΔ,α::E,Δ0τ2::TΔ,α::E,Δ0ρ::R
Δμα.Δ0.τ1τ2/ρ::E
K-C0

Terms add control0 k.e and ex.er; evaluation contexts add Ex.er, with (n+1)-free(E)n-free(Ex.er). The capture and return roots are E[control0 k.e]x.er0e[(λz.E[z])/k](0-free(E)),vx.er0er[v/x]. Unlike shift0, this continuation omits the reset and return clause. The full control rule is

ε=μα.Δ0.τ1τ2/ρΔ,Δ0;Γ,k:τερ[ε/α]τ1[ε/α]e:τ2[ε/α]/ρ0Δ,Δ0ρ0<:ρΔτ::TΔερ0::R
Δ;Γcontrol0 k.e:τ/ερ0
C0-Control

For reset, put δε=δ[αε], τe=δετ1, τr=δετ2, and ρr=δερ. Then

ε=μα.Δ0.τ1τ2/ρΔδ::Δ0Δ;Γe:τe/ερrΔ;Γ,x:τeer:τr/ρr
Δ;Γex.er:τr/ρr
C0-Reset

Definition 35.32 — Shallow bridge translations

The prefix SH- on a rule name denotes a shallow-handler rule, whereas SH() below denotes the forward syntax translation; the parentheses and rule-name typography keep the two roles distinct. The named translations SH() from control0 to shallow handlers and SD() back have term clauses SH(control0 k.e)=do(λk.SH(e)),SH(ex.er)=handle SH(e){f,r.fr; x.SH(er)},SD(do v)=control0 k.λh.hSD(v)k,SD(handle e{x,r.eh; y.er})=SD(e)y.λh.SD(er)(λx.λr.SD(eh)). The forward single-effect clause is SH(μα.Δ0.τ1τ2/ρ)=μα.β::T.(Δ0.(βαSH(ρ)SH(τ1))SH(ρ)SH(τ2))β, For the reverse direction, define the four-argument metanotation H(a,b1,b2,g):=Δ0.SD(τ1)(SD(τ2)agb1)gb2. Its four arguments make explicit the variables that vary across uses; Δ0,τ1,τ2 are fixed by the ambient effect being translated. The reverse single-effect clause is SD(μα.Δ0.τ1τ2)=μα.β1::T,β2::T,γ::R.β1(H(α,β1,β2,γ)γβ2)/γ.

Lemma 35.33 — Shallow-bridge type substitution

For X{SH,SD}, X(τ[σ/α])=X(τ)[X(σ)/α].

Proof of Lemma 35.33 — Shallow-bridge type substitution

Proof. Induct on types and rows. The recursive-effect case uses capture-avoiding unfolding δε=δ[αε], after renaming every bound variable away from the substitution support. ◻

Lemma 35.34 — Shallow-bridge value substitution

For X{SH,SD}, X(e[v/x])=X(e)[X(v)/x].

Proof of Lemma 35.34 — Shallow-bridge value substitution

Proof. Induct on terms, alpha-renaming each term binder away from x and FV(v) before descent. Choose k,hFV(SD(v)){x}. Then SD((dow)[v/x])=control0 k.λh.hSD(w[v/x])k, and the induction hypothesis makes this exactly SD(dow)[SD(v)/x]. The shallow handler clause follows by the same capture-avoiding calculation. ◻

Lemma 35.35 — Shallow-bridge context compatibility

For X{SH,SD}, X(E[e])=X(E)[X(e)]. Translation also preserves every derivable n-freeness judgment.

Proof of Lemma 35.35 — Shallow-bridge context compatibility

Proof. Use simultaneous induction on the evaluation-context and freeness derivations. A shallow handler and a control0 reset each add exactly one delimiter; their translated contexts therefore have the same freeness index. ◻

Lemma 35.36 — Shallow-bridge translation algebra

The shallow translations commute with type substitution, value substitution, and context plugging, and preserve delimiter freeness.

Proof of Lemma 35.36 — Shallow-bridge translation algebra

Proof. Combine lemma 35.33, lemma 35.34, lemma 35.35. ◻

For a shallow handler, the corresponding plain-k clause is well typed: SD(do v)=control0 k.λh.hSD(v)k. Unlike the failed deep attempt above, invoking k must not reinstall the handler. This contrast determines the operation clause rather than merely confirming it after translation.

Lemma 35.37 — Forward shallow-bridge preservation

The forward translation in definition 35.32 preserves kinding and subtyping. For every well-formed Δ,Γ,τ,ρ, a control0 source judgment implies Δ;Γe:τ/ρΔ;SH(Γ)SH(e):SH(τ)/SH(ρ),

Proof of Lemma 35.37 — Forward shallow-bridge preservation

Proof. Induct simultaneously on forward formation, subtyping, and typing, using the substitution component of lemma 35.36 at each binding boundary.

Formation. Variable, arrow, universal, empty-row, and row-cons formation and all common-core typing cases are homomorphic. We derive the recursive effect-formation cases and the novel typing cases here, without appealing to another correspondence theorem. In the control-to-handler direction use SH(). For the source types τ1,τ2 and answer row ρ, put T1:=SH(τ1),T2:=SH(τ2),R:=SH(ρ). Then write η=μa.b::T.Qbb,Qb=Δ0.((baRT1)RT2). For the K-C0-to-K-SH formation case, write Ξ=Δ,a::E,b::T,Δ0. The translated source premises provide ΞTi::T and ΞR::R. The target derivation begins Ξa::EΞR::RΞaR::RKCons, Ξb::TΞaR::RΞT1::TΞbaRT1::TKArr, ΞbaRT1::TΞR::RΞT2::TΞ(baRT1)RT2::TKArr. Iterated universal formation and recursive shallow-effect formation are Ξ(baRT1)RT2::TΔ,a::E,b::TQb::TKAllΔ0, Δ,a::E,b::TQb::TΔ,a::E,b::Tb::TΔη::EKSH. Thus K-Cons gives Δηι::R.

Control-to-handler typing.

Unfolding the recursive effect is kind-preserving substitution. Put Ti=Ti[η/a],R=R[η/a],Qb=Qb[η/a]. The preceding derivation therefore gives Δ,b::TQb::T,Δ,Δ0ηR::R, where, in full, Qb=Δ0.((bηRT1)RT2). In the typing derivation below we drop overlines on Ti,R, but retain Q. Thus a translated C0-Control premise, for result type U, is Δ,Δ0;Γ,k:UηRT1SH(e):T2/R0,R0<:R. Rule P-Sub widens the body’s row to R; P-Lam and one P-Gen for each member of Δ0 then give the following judgment. Alpha-rename the binders of Δ0 away from FV(Γ) first, so every generalization satisfies the freshness premise: Γλk.SH(e):QU/ι. Rule SH-Do unfolds η, instantiates bU, and gives result U/η. The final P-Sub uses ι<:R0 under Sub-Cons, yielding U/ηR0.

At a translated C0-Reset, let δ::Δ0 be the source instantiation. The operation arm of SH-Handle has f:Qb,r:bηδRδT1. Instantiating f by δ gives f:(bηδRδT1)δRδT2. After pure-row widening, P-App derives fr:δT2/δR. Together with the two induction hypotheses this is exactly SH-Handle; no deep handler is silently used. ◻

Lemma 35.38 — Reverse shallow-bridge preservation

The reverse translation in definition 35.32 preserves kinding and subtyping. For every well-formed Δ,Γ,τ,ρ, a shallow-handler source judgment implies Δ;Γe:τ/ρΔ;SD(Γ)SD(e):SD(τ)/SD(ρ).

Proof of Lemma 35.38 — Reverse shallow-bridge preservation

Proof. Induct simultaneously on reverse formation, subtyping, and typing. Common constructors are homomorphic, and substitution uses lemma 35.36. The recursive effect, operation, and handler cases are the following explicit derivations.

For K-SH-to-K-C0, first keep the recursive variable abstract: Fb1,ga=SD(τ2)agb1,Gb1,b2,ga=Fb1,gagb2, H(a,b1,b2,g)=Δ0.(SD(τ1)ιGb1,b2,ga), θ=μa.b1::T,b2::T,g::R.b1(H(a,b1,b2,g)gb2)/g. Put Ω=Δ,a::E,b1::T,b2::T,g::R,Δ0. The translated K-SH premises and the fresh-variable rules yield the following derivation: Ωa::EΩg::RΩag::RKCons. Ωτ2::TΩag::RΩb1::TΩFb1,ga::TKArr. ΩFb1,ga::TΩg::RΩb2::TΩGb1,b2,ga::TKArr, Ωτ1::TΩι::RΩGb1,b2,ga::TΩτ1ιGb1,b2,ga::TKArr. Writing Ω0=Δ,a::E,b1::T,b2::T,g::R, the final formation steps are Ωτ1ιGb1,b2,ga::TΩ0H(a,b1,b2,g)::TKAllΔ0, Ω0H(a,b1,b2,g)::TΩ0g::RΩ0b2::TΩ0H(a,b1,b2,g)gb2::TKArr, Ω0b1::TΩ0H(a,b1,b2,g)gb2::TΩ0g::RΔθ::EKC0. In particular, K-Cons gives Δθι::R. Unfolding is again kind-preserving substitution. With τi=τi[θ/a],Hb1,b2,g=H(a,b1,b2,g)[θ/a], we have Hb1,b2,g=Δ0.(τ1ι((τ2θgb1)gb2)), Δ,b1::T,b2::T,g::RHb1,b2,g::T. Δ,b1::T,b2::T,g::Rθg::R. Below we omit overlines on τi, but retain H. For a translated SH-Do, specialize h:Hb1,b2,g by the source substitution δ0::Δ0. With k:δ0τ2θgb1 the two applications derive hSD(v)k:b2/g,λh.hSD(v)k:Hb1,b2,ggb2/ι. Rule C0-Control now instantiates its recursive effect by fresh b1,b2,g, uses ι<:g, and concludes δ0τ2/θι.

For a source shallow handler with result τr/ρ, set Hr=Δ0.τ1(τ2θρτ)ρτr. The translated reset explicitly instantiates [b1τ,b2τr,gρ]. Its body has type τ/θρ, and its return clause x.λh.SD(er) has type Hrρτr/ρ. Rule C0-Reset therefore returns a function of that type. Generalizing λx.λr.SD(eh) over Δ0 gives Hr/ι; one row-widened P-App gives τr/ρ. Thus b1 is the type expected by the captured continuation, b2 is the handler result, and g is the residual row, with every instantiation exposed. ◻

Lemma 35.39 — Shallow-bridge root simulation

Both shallow translations preserve one source step by a nonempty target evaluation sequence: eeSH(e)+SH(e),eeSD(e)+SD(e). No arbitrary-context relation is needed in either direction.

Proof of Lemma 35.39 — Shallow-bridge root simulation

Proof. For the control-to-handler root, put D={f,r.fr; y.SH(er)},vc=λz.SH(E)[z]. Then SH(E[control0 k.e]y.er)=handle SH(E)[do(λk.SH(e))]D(λk.SH(e))vcβSH(e)[vc/k]=SH(e[(λz.E[z])/k]). The shallow resumption contains neither handler nor reset.

For the reverse root, put H=λx.λr.SD(eh),Ry=λh.SD(er). The whole calculation is SD(handle E[do v]{x,r.eh; y.er})=SD(E)[control0 k.λh.hSD(v)k]y.RyH(λh.hSD(v)(λz.SD(E)[z]))Hβ+HSD(v)(λz.SD(E)[z])β+SD(eh)[SD(v)/x,(λz.SD(E)[z])/r]=SD(eh[v/x,(λz.E[z])/r]). Every administrative contraction is in evaluation position. Return roots translate to return roots, while common beta and lift roots are homomorphic. The substitution, context, and freeness parts of lemma 35.36 lift the calculations and complete both positive simulations. For a source context step E[e]E[e], the plugging equation rewrites X(E[e]) to X(E)[X(e)]; target compatible closure lifts the root calculation, and plugging rewrites its endpoint to X(E[e]). ◻

Theorem 35.40 — Typed shallow/ control_0 correspondence

For every well-formed Δ,Γ,τ,ρ, a control0 source judgment implies Δ;Γe:τ/ρΔ;SH(Γ)SH(e):SH(τ)/SH(ρ), while a shallow-handler source judgment implies Δ;Γe:τ/ρΔ;SD(Γ)SD(e):SD(τ)/SD(ρ). Both also preserve one source evaluation step by a nonempty sequence of ordinary evaluation-context steps in the target: eeSH(e)+SH(e),eeSD(e)+SD(e). No arbitrary-context relation is needed in either shallow direction.

Proof of Theorem 35.40 — Typed shallow/ control_0 correspondence

Proof. The two typing implications are lemma 35.37, lemma 35.38; the two positive simulations are lemma 35.39. ◻

A fully typed shallow root.

Again fix A::T and a:A, and define εc=μq..AA/ι,εh=μq.b::T.((bqιA)ιA)b. For cA=control0 k.ax.x, the capture premise and conclusion are

εc=μq..AA/ι
a:A,k:AεcιAa:A/ι
P-Var
ι<:ιA::Tεcι::R
a:Acontrol0 k.a:A/εcι
C0-Control

Together with A::T:: and a:A,x:Ax:A/ι, rule C0-Reset gives a:AcA:A/ι. This lists every premise of the two novel source rules.

The translated operation is do(λk.a). Rule P-Var types the body a, and P-Lam abstracts k, giving a:Aλk.a:(AεhιA)ιA/ι. Rule SH-Do unfolds εh and instantiates bA, deriving a:Ado(λk.a):A/εh. In the operation arm, under fresh b::T, f:(bεhιA)ιA,r:bεhιAfr:A/ι by two P-Var leaves and P-App; the return arm is x:Ax:A/ι. Hence SH-Handle completely derives a:Ahandle do(λk.a){f,r.fr; x.x}:A/ι. The source and target roots expose the shallow resumption: cAcapturea,handle do(λk.a){f,r.fr; x.x}handlercapture(λk.a)(λz.z)βa. The target resumption is λz.z, with no reinstalled handler.

The difference becomes observable when resumption itself reaches another operation. For this calculation only, conservatively extend the shared PPS core with the pure unit type Unit, its value (), and typed lambda annotations; none of the correspondence theorems depends on this example. At the respective deep and shallow signatures for the unit operation UnitUnit, let P:=handle ((λu:Unit.do())(do())){x,r.r(); y.y}. For a deep handler, handling the first operation passes the reinstalled continuation λz.handle ((λu:Unit.do())z){x,r.r(); y.y} to the clause. Invoking it puts the second do beneath the same handler, so the second operation is handled and the computation returns (). For a shallow handler, the corresponding resumption is only λz.(λu:Unit.do())z. Invoking it reduces to the unhandled operation do(). Thus reinstalling the delimiter is not administrative syntax: it determines whether a later operation is caught.

The result is signature-specific. It requires four things at once: ordered rows; quantification over all three kinds; polymorphic deep effects; and recursive shallow effects. The four proved translations are shift0 DH deep,deep DD shift0,control0 SH shallow,shallow SD control0. Each arrow states type preservation and the simulation relation printed in the corresponding theorem. No inverse, retraction, reflection, or full- abstraction theorem is proved. Swapping either pairing, or erasing the quantifiers, changes the resumption type and invalidates the typing proof.

Exercise 35.6

★☆☆ Make a four-row table for the translations in theorem 35.30, theorem 35.40. For each row, record whether the captured continuation reinstalls its delimiter, whether the target effect is recursive, and whether semantic preservation uses ordinary evaluation reduction or arbitrary-context reduction. Justify every entry from one displayed term or effect clause.

Dependent elimination: a separate failure boundary

This boundary calculation uses a strong existential, which packages a witness together with a certificate whose type may mention that very witness: from p:x:Nat.A, its projections have types witp:Nat and prfp:A[witp/x].

Simple types do not let a result type inspect the particular value returned by a computation. A dependent pair does. Control can make the same proof return different witnesses in different continuations, so unrestricted dependent elimination can confuse a witness with its certificate.

The counterexample in this section is not a run of the call-by-value machine λK. It belongs to a separate hypothetical call-by-name calculus with a commuting rule for first projection. Its purpose is to isolate exactly which extra dependent rule is dangerous.

Definition 35.41 — The hypothetical commuting-projection calculus

Separate number terms from proof terms, and distinguish proof values: A,B::=t=ux:Nat.A,t,u::=xnwitpcallcckt,p,q::=arefl(t,p)prfpsubst p qcallcckpthrowkpthrowkt,V::=arefl(t,V). Contexts are sorted: Γ::=Γ,x:NatΓ,a:AΓ,k÷AΓ,k÷Nat. The entry k÷A names a proof continuation accepting an A-proof, and k÷Nat names a number continuation. The typing rules print these as k:¬A and k:Nat for readability; they are continuation names, not formulas in the formula grammar and not ordinary function assumptions. Formula formation and the variable leaves are

Γ prop
Dep-Bot-F
Γt:NatΓu:Nat
Γt=u prop
Dep-Eq-F
Γ,x:NatA prop
Γx:Nat.A prop
Dep-Ex-F
nN
Γn:Nat
Dep-Nat
x:NatΓ
Γx:Nat
Dep-Var-N
a:AΓ
Γa:A
Dep-Var-P

The strong-existential and equality rules are

Γt:NatΓp:A[t/x]
Γ(t,p):x:Nat.A
Dep-Pair
Γp:x:Nat.A
Γwitp:Nat
Dep-Wit
Γp:x:Nat.A
Γprfp:A[witp/x]
Dep-Prf

Let be the least compatible one-step relation generated by the projection, substitution, commuting, and vacuity clauses displayed in this definition. Number-term convertibility tu is the least reflexive, symmetric, transitive relation containing ; formula convertibility is its congruential extension. Equality has

tu
Γrefl:t=u
Dep-Refl
Γp:t=uΓq:B[t/x]
Γsubst p q:B[u/x]
Dep-Subst

There is also the conversion rule Γp:AABΓp:BDepConv. The ordinary projection contractions are wit(t,p)t,prf(t,p)p,subst refl pp. At proof type, the naive classical rules, including their arbitrary throw result, are

Γ,k:¬Ap:A
Γcallcckp:A
Dep-Callcc-P
Γ,k:¬Ap:AΓB prop
Γ,k:¬Athrowkp:B
Dep-Throw-P

The assumption k:¬A names a captured proof evaluation context whose hole accepts a proof of A and whose command has answer sort ; it can be invoked only by throwk. The mixed forms callcckt and throwkt exist only so that first projection can commute with control: the former captures a number context, and the latter is a proof-level escape to it with an arbitrary local proof type. Writing for the answer sort, their schematic rules are

Γ,k:Natt:Nat
Γcallcckt:Nat
Dep-Callcc-N
Γ,k:Natt:NatΓB prop
Γ,k:Natthrowkt:B
Dep-Throw-N

Here k:Nat likewise names a captured number context whose hole accepts a natural number; it is not a lambda-bound function. Define p[kwit/k] to replace each throwkq in p by throwk(witq). This operation changes the sort of the named continuation: before commuting, k÷A accepts an existential proof; afterward, k÷Nat accepts its witness. Both control rules are present precisely to type that change. The commuting and vacuity clauses used here are wit(callcckp)callcck(wit(p[kwit/k])),callccktt(kFV(t)),callcckpp(kFV(p)). The vacuity, or eta, clause is load-bearing: Herbelin’s Corollary 5 shows that the explicitly typed control system without this clause avoids the Sigma-type collapse, whereas adding the clause to that same typed system admits the calculation below (Proposition 6, §2.6); untyped control names collapse already without it (§2.3) [Her05]. Reduction is closed under either sort of callcc: ttcallccktcallcckt,ppcallcckpcallcckp. These compatibility clauses are part of the hypothetical call-by-name fragment; in particular, they authorize the projection step beneath the number-level callcc below. Reduction does not inspect the certificate in (t,p) before a projection demands it. No confluence theorem is claimed for , and the counterexample uses only the two forward reductions displayed below. Thus its conclusion does not rest on choosing normal forms for the convertibility relation. The first rule moves the context wit([]) through the captured continuation. It has no counterpart in the call-by-value stack machine.

The unrestricted projection grammar is unsafe. Its value-restricted variant replaces witp and prfp by witV and prfV, using the proof-value grammar above. Equivalently, rules Dep-Wit and Dep-Prf require their existential premise to be a syntactic V. Neither a callcc term nor p0 below is a proof value.

Consider p0:=callcck(0,throwk(1,refl)):x:Nat.x=1. Every node of its typing is visible in the following chain. Under k:¬(x:Nat.x=1), 1:Nat,refl:1=1,(1,refl):x:Nat.x=1. Rule Dep-Throw-P assigns throwk(1,refl) the arbitrary local result type 0=1. With 0:Nat, rule Dep-Pair therefore gives (0,throwk(1,refl)):x:Nat.x=1, and Dep-Callcc-P discharges k, yielding the type in (35.2). The commuting rule and the explicit compatibility clause under callcc give the call-by-name reduction witp0callcck(wit(0,throwk(wit(1,refl))))callcck00. The projection typing and this conversion yield prfp0:witp0=1,refl:witp0=0. Substitution of equals produces a proof of 1=0. The same occurrence p0 supplied the witness in one continuation and the certificate in another. This collapses two distinct closed numerals. The fragment has no rule from 1=0 to , so the precise conclusion is failure of numerical canonicity, not a separately derived proof of falsehood.

Proposition 35.42 — The dependent-elimination boundary

The hypothetical calculus of definition 35.41 derives 1=0. This result does not run (35.2) in λK and does not refute a call-by-value, value-restricted dependent control calculus. The failure is the derivation in which witp0 reduces to 0 while prfp0 retains the certificate obtained with witness 1. Requiring Dep-Wit and Dep-Prf to eliminate only a proof value rejects p0, which contains callcc.

Proof of Proposition 35.42 — The dependent-elimination boundary

Proof. Let B(x):=x=0. The first displayed proof has type witp0=1, while conversion along witp00 types refl:witp0=0=B(witp0). Rule Dep-Subst therefore derives subst(prfp0)refl:B(1),that is,1=0. The simple CPS theorem cannot be applied: its source has neither dependent projection nor the commuting call-by-name rule. A value restriction blocks both witp0 and prfp0, because p0 is not a proof value. Soundness of any broader purity class requires a separate preservation argument. ◻

Exercise 35.7

★★★ Derive the type of every subterm of p0, including the arbitrary result type assigned to its throw. Write all three reduction steps—the commuting step, the pair projection under callcc, and the vacuous-callcc step—including the transformed throw payload. Then derive, from the two displayed projection typings, subst(prfp0)refl:1=0. Identify the rule absent from the call-by-value λK machine.

ML polymorphism: a third, disjoint boundary

The ML comparison concerns neither the stack machine nor dependent elimination. It asks whether Damas–Milner let-generalization remains sound after continuations become first-class ML values. The answer depends on the operational treatment of let, so the syntax, type assignment, and evaluator are fixed together.

Convention 35.43 — The ML-continuation firewall

The definitions, counterexample, soundness theorem, and exercise in this subsection belong to the closed, purely functional ML core of Harper, Duba, and MacQueen, extended only by callcc and throw. Its continuation type τcont, its evaluator continuations, and its fixed answer monotype are local to this subsection. They are not the ContA values of λK, the ordered effects of section 35.4, or the proof continuations of subsection 35.4.4. The result concerns neither state, exceptions, handlers, dependent types, answer-type polymorphism, or a modern value restriction for a larger language.

Write e1;e2:=(λz.e2)e1, where zfv(e2). The unsoundness result and this closed ML expression are due jointly to Lillibridge and Harper [HDM93]: let f be callcc(λk.λx.throw k (λy.x)) in f1;ftrue. Unrestricted assignment gives this expression type bool, while the continuation evaluator returns 1. The local rules and the complete derivations make both statements precise.

The local expressions, monotypes, and polytypes are e::=xcλx.ee1e2let x be e1 in e2,τ::=btτ1τ2τcont,σ::=τt.σ. Here b ranges over base types, including int and bool, while t ranges over type variables. Apart from callcc and throw, constants have base monotypes. Write στ when τ is a monotype instance of σ, and write σ1σ2 when every monotype instance of σ2 is an instance of σ1. This relation is reflexive and transitive by inclusion of instance sets. Define CloseΓ(τ):=t1tn.τ,{t1,,tn}=ftv(τ)ftv(Γ). Let Σ be the fixed constant signature. The complete type-assignment card is

Γ(x)τ
Γx:τ
ML-Var
Σ(c)τ
Γc:τ
ML-Const
Γ,x:τ1e:τ2xdom(Γ)
Γλx.e:τ1τ2
ML-Abs
Γe1:τ2τΓe2:τ2
Γe1e2:τ
ML-App
Γe1:τ1Γ,x:CloseΓ(τ1)e2:τ2xdom(Γ)
Γlet x be e1 in e2:τ2
ML-Let

The continuation primitives have exactly the polytypes callcc:t.(tcontt)t,throw:s.t.scontst. Thus the result type of a throw is arbitrary, but the value sent to the saved continuation has the saved continuation’s argument type.

The operational judgment is Kea. Its closed values, evaluation continuations, and answers are v::=cλx.ethrow vK,K::=[]KevKlet x be K in e,a::=vwrong. The partial application throw v is a value because throw is curried; an evaluator continuation K is a reified run-time value, not a surface expression. Fix one answer monotype α. Its free type variables are rigid parameters of the operational metatheory: admissible type substitutions fix them. Quantified prefixes are alpha-renamed away from those parameters. For that theorem instance, Close quantifies only flexible variables, while and range over admissible substitutions fixing the rigid answer parameters. Outside the fixed-answer operational argument, the surface assignment rules retain their ordinary meaning. Reified-continuation typing is defined only at that boundary: K:τcontx:τK[x]:α. The surface type τcont does not quantify over α. If K has one hole, write K[e] for plugging e into that hole and K[K] for continuation composition. The complete evaluator card is

[]vv
val0
[]K[v]aK[]
Kva
val1
K[[]e2]e1ae1 is not a value
Ke1e2a
fn
K[v1[]]e2ae2 is not a value
Kv1e2a
arg
Ke1[v2/x]a
K(λx.e1)v2a
beta
v1 is headed by neither λ nor callcc nor throw
Kv1v2wrong
wrong
K[let x be [] in e2]e1ae1 is not a value
Klet x be e1 in e2a
bind
Ke2[v1/x]a
Klet x be v1 in e2a
sub
KvKa
Kcallcc va
seize
Kva
Kthrow K va
jump

Rule seize passes the current K to its operand. Rule jump discards the current K and resumes K. In wrong, “headed by throw” includes the partial application throw K, whose next argument is governed by jump. These clauses are the continuation semantics at the fixed answer boundary, not a reduction semantics for λK.

For the counterexample, Σ(1)=int and Σ(true)=bool. Unrestricted ML-Let is unsound with this evaluator. Let q:=λk.λx.throw k (λy.x),E:=callcc q,C[f]:=f1;ftrue,P:=let f be E in C[f], where the sequencing abbreviation uses application, not let. Thus P is the expression in (35.3). Every type in the critical derivation can be read off from one type variable u. Under k:(uu)cont and x:u, choose y:u. Then λy.x:uu,throw k (λy.x):u,λx.throw k (λy.x):uu. The throw uses (35.4) at s=uu and local result t=u. Instantiating callcc at t=uu gives q:(uu)cont(uu),E:uu. Because the outer context is empty, ML-Let assigns f:u.uu. Its first occurrence is instantiated at int, its second at bool; hence f1:int,ftrue:bool,λz.ftrue:intbool,C[f]:bool,P:bool.

The evaluator nevertheless returns 1. Put K0:=let f be [] in C[f],g:=λx.throw K0 (λy.x),h:=λy.1, and let K1:=(λz.gtrue)[] be the sequence continuation waiting for the result of the first call g1, and put K2:=(λz.htrue)[]. The following is the spine of the evaluation derivation; each double arrow replaces a conclusion by the premise selected by the named operational rule, rather than introducing a new reduction relation: []P1bindK0E1seizeK0qK01betaK0g1val1[]let f be g in C[f]1sub[]C[g]1argK1g11betaK1throw K0 h1jumpK0h1val1[]let f be h in C[f]1sub[]C[h]1argK2h11betaK211val1[](λz.htrue)11beta[]htrue1beta[]11. The final judgment is the axiom val0. The first call of g throws h=λy.1 back to the let frame K0. Rule sub substitutes h for f and executes both calls. The original static derivation licensed the two occurrences of f at distinct instances, but dynamically h is only the integer constant function, so htrue also returns 1. For the diagnostic only, extend the signature with not:boolbool, its two Boolean evaluation clauses, and a clause returning wrong on a non-Boolean argument. Then not P:bool, but evaluation first obtains 1 from P and the new primitive clause returns wrong. This temporary observation primitive is not part of the fixed signature used in theorem 35.46.

The failed invariant is now explicit. The saved frame K0 needs its hole at the polytype u.uu, because the body uses f at two instances. Equation (35.5), however, can give a reified continuation only a monotype argument. Fixing the answer monotype α=bool for this counterexample, the unrestricted proof has x:Close(uu)K0[x]:bool, not the premise x:uuK0[x]:bool required by seize.

One exact repair restricts the language itself to values-only let: every term let x be e1 in e2 must have a syntactic value e1. The bind rule and the continuation form let x be K in e then disappear; value-bound lets still use ML-Let and may still generalize. This is the paper’s values-only language, not the distinct policy which retains arbitrary let right-hand sides but generalizes only selected ones.

Lemma 35.44 — Admissible type substitution

If Γe:τ and an admissible type substitution S fixes ftv(Γ)ftv(α), then Γe:Sτ.

Proof of Lemma 35.44 — Admissible type substitution

Proof. Induct on typing. Variable and constant instances compose their witnessing instantiations with S. Abstraction and application use the induction hypotheses on their premises. In a let, alpha-rename the variables closed by CloseΓ away from the domain and range of S. Since S fixes Γ, closing the substituted right-hand-side type binds the same renamed variables; apply the two induction hypotheses and rebuild ML-Let. Fixing the rigid answer parameters ensures that the reified-continuation boundary (35.5) is unchanged. ◻

Lemma 35.45 — Generalized Damas–Milner substitution

At the fixed answer monotype α, suppose Γw:ρ, Γ,z:σe:τ, and CloseΓ(ρ)σ. Then Γe[w/z]:τ.

Proof of Lemma 35.45 — Generalized Damas–Milner substitution

Proof. First, replacing a declaration by a more general scheme preserves typing. This follows by induction on the typing derivation: at the replaced variable, transitivity of preserves every monotype instance selected by ML-Var; constants, lambdas, and applications rebuild immediately. In a let case, a more general context has no additional free type variables, so its closure of the right-hand-side monotype is at least as general as the old closure. Apply the same replacement fact to the let-bound declaration in the body, then rebuild with ML-Let.

For substitution, prove the stronger claim with an arbitrary additional context Δ, disjoint from z: from Γ,Δ,z:σe:τ, derive Γ,Δe[w/z]:τ. Induct on the displayed typing derivation. If the last rule is ML-Var at z, then στ. Since CloseΓ(ρ)σ, also CloseΓ(ρ)τ. Hence a substitution S of monotypes for the variables closed in ρ, fixing ftv(Γ)ftv(α), satisfies ρ[S]=τ. Lemma 35.44 applied to Γw:ρ gives Γw:τ, and weakening adds Δ. A different variable and a constant are unchanged.

Rules ML-Abs and ML-App follow by extending Δ with the lambda binder, using exchange for the finite-map contexts, and applying the induction hypotheses. In the ML-Let case, apply the first induction hypothesis to its right-hand side. For its body, the induction hypothesis initially retains the scheme CloseΓ,Δ,z:σ(ρ1) of the let variable. Removing z:σ can only quantify more variables, so CloseΓ,Δ(ρ1)CloseΓ,Δ,z:σ(ρ1). The scheme-strengthening fact from the first paragraph replaces the retained declaration by CloseΓ,Δ(ρ1); ML-Let then rebuilds the substituted term.

The run-time value clause for a reified continuation is stable under the same infrastructure. From K:ρcont obtain x:ρK[x]:α. A type substitution S gives x:ρ[S]K[x]:α, because S fixes the rigid variables of the answer monotype; hence K:ρ[S]cont. In the term-substitution induction a reified K is unchanged because run-time continuations are term-closed. The partial-throw value uses ML-App. Thus the five surface rules and both additional run-time value forms are covered. Taking Δ= proves the statement. ◻

Theorem 35.46 — Values-only soundness

Fix an answer monotype α, whose free variables are rigid as specified at (35.5). In the ML-continuation language with values-only let, suppose e:τ, x:τK[x]:α, and Kea. Then a is a value and a:α. Consequently a closed well-typed program evaluated in the empty continuation cannot produce wrong.

Proof of Theorem 35.46 — Values-only soundness

Proof. Use induction on the evaluation derivation. Each substitution step uses lemma 35.45. Its monomorphic specialization takes σ=ρ; its value-bound-let specialization takes σ=Close(ρ).

For val0, the premise x:τ[][x]:α forces the hole to be used at α, so substitution gives v:α. For val1, monomorphic plugging gives K[v]:α; apply the induction hypothesis to []K[v]a. In the fn case, inversion gives e1:ρτ and e2:ρ. The pushed continuation has y:ρτK[ye2]:α, so the induction hypothesis applies to evaluation of e1. The arg case is the same calculation with y:ρK[v1y]:α. The beta case follows from monomorphic substitution. The sub case follows from value substitution at Close(ρ). There is no bind case.

For seize, inversion of callcc v:τ gives v:τcontτ. The theorem’s continuation premise and (35.5) give K:τcont; hence vK:τ, and the induction hypothesis applies to the rule premise. For jump, inversion of throw K v:τ gives a monotype ρ with K:ρcont and v:ρ. By the definition of reified continuation typing, z:ρK[z]:α, so the induction hypothesis applies to Kva. Finally, a well-typed operator value of arrow type is a lambda, callcc, or a partial application of throw; a reified continuation has a cont type. Thus the wrong rule cannot close a typed derivation. These cases exhaust the evaluator and establish a:α, which excludes the only nonvalue answer wrong. For the corollary take K=[] and α=τ, after standardizing the typing derivation’s quantified and flexible variables apart from ftv(τ); then regard ftv(τ) as the rigid parameters of that theorem instance. ◻

The repair blocks P before evaluation: its let right-hand side E is an application of callcc, not a syntactic value. The theorem does not identify every safe nonvalue and does not license a purity or effect generalization beyond the language fixed in convention 35.43. It is the values-only half of the soundness result proved by Harper, Duba, and MacQueen [HDM93].

Exercise 35.8

★★☆ In the values-only language, derive let i beλz.z in i1;itrue:bool. Then, for the unrestricted evaluator’s saved continuation K0, show that no monotype τ makes x:τK0[x]:bool derivable, even though assigning x:u.uu does. This is a metatheoretic check of the unrestricted continuation, not a term admitted by values-only let. Identify the rejected syntactic node in P, and explain why replacing its body by f1;f1 does not make P admissible under the selected repair.

Suggested first pass.

Begin with exercise 35.9, exercise 35.10. Continue with exercise 35.11 and exercise 35.8; then use the remaining problems to test the normalization, dependent, and ML-polymorphism boundaries. No problem in this seminar is a prerequisite for a later chapter.

Exercise 35.9

★★☆ Let K=;add3[];[]7, where the top frame expects a function NatNat. Derive K:(NatNat)Nat, compute Khk, and verify its target type binder by binder. Then run the source and target states for the returned value λx:Nat.x.

Exercise 35.10

★★★ Fix K:AR and a closed value a:A. Let C be the case frame case [] of {inlxx;inrqthrow a to q}, so C:(A+ContA)A. Derive K;C:(A+ContA)R, start lemA under that stack, and splice both traces of proposition 35.16 into one uninterrupted run ending at Ka. Translate the case frame and the restored injection frame to CPS and check the corresponding target reductions.

Exercise 35.11

★★☆ For a 0-free context E, expand the translation of E[shift0 k.e]x.er. Perform the deep-handler contraction and every beta step until the translation of e[(λz.E[z]x.er)/k] appears. State where 0-freeness is used.

Exercise 35.12

★☆☆ Construct a fictitious translation which maps every source state to one fixed target value. It satisfies a zero-or-more-step simulation. Explain formally why it cannot prove theorem 35.13, and identify the exact use of positivity in that proof.

Exercise 35.13

★☆☆ Impose the syntactic rule that witp and prfp are formed only when p is a value. Show precisely which two expressions in the numeral-collapse derivation become ill formed. Explain why this observation blocks that derivation but is not, by itself, a preservation proof for a complete dependent control calculus.

Exercise 35.14

★★★ Practical project.control-machine-cps Implement the finite stack machine and its represented CPS spine in Kappa. Maintain the invariant that a captured stack is the complete ordered frame list and that applying a saved continuation replaces, rather than extends, the current list; the saved list remains available to later callers. The four discriminatory cases must check complete-spine capture, stack replacement on throw, persistence across two callers, and the two-stage LEM change-of-mind trace. Direct/CPS equality, the declared positive costs, and the DNE constructor equality are smoke outputs only, not independent evidence. Mutate capture to retain only the top frame; the mutant must still type-check and audit cleanly but fail the complete-spine and LEM cases. Appendix E records the four acceptance commands, and appendix F gives the construction stages.

Sources.

Griffin identified typed control with classical proofs [Gri90]. Harper develops the stack-machine and CPS background [Har16]. Kameyama and Hasegawa give the equational boundary for delimited control [KH03]. The four typed correspondences between handlers and control follow Piróg, Polesiuk, and Sieczkowski [PPS19]. Herbelin gives the Sigma-type counterexample and identifies the vacuity rule as the load-bearing boundary [Her05]; Miquey supplies the sequent-calculus presentation and value-restriction repair used here [Miq19].

Harper, Duba, and MacQueen supply the ML evaluator and exact values-only theorem; their footnote 3 credits the polymorphic counterexample jointly to Lillibridge and Harper [HDM93].

Search the book

Type to search the local edition.