在計算機科學中,續體(英語:continuation,也譯作計算續體、續延、延續性),是對計算機程序的控制狀態的一種抽象表示。續體實化了程序控制狀態。可以理解為,續體是一種數據結構,它表示在進程執行中給定點上的計算過程,所建立的數據結構可以被編程語言訪問,而不是被運行時環境所隱藏掉。這對實現編程語言的某些控制機制,比如例外處理、協程、生成器非常有用。
「當前續體」從運行代碼的角度看來,是可以從程序執行的當前點導出的續體。續體還被用來提及「頭等續體」,它是一種構造,賦予編程語言保存在任何點上的執行狀態的能力,並在程序中後來的點上可能多次返回到這一點。
Gerald J. Sussman和Guy L. Steele Jr.在1976年論文《Lambda: The Ultimate Imperative》中,認定Alonzo Church在1941年著作《The Calculi of Lambda-Conversion》裡[1],已經清晰的理解了「續體」的使用,這是用純λ演算徹底完成所有事情即邱奇編碼的唯一方式,並舉出其中有序對定義所對應的Scheme表示為例[2]:
(define (cons m n)
(lambda (a) (a m n)))
(define (car a)
(a (lambda (b c) b)))
(define (cdr a)
(a (lambda (b c) c)))
John C. Reynolds在1993年的論文《The Discoveries of Continuations》中給出了發現續體的完整歷史。Reynolds認為續體的最早描述由Adriaan van Wijngaarden在1964年9月作出。Van Wijngaarden在奧地利的維也納附近巴登召開的關於「形式語言描述語言」的IFIP工作會議上,在關於ALGOL 60預處理器的公式化的論文《Recursive Definition of Syntax and Semantics》中,倡導通過將真正(proper)過程變換成續體傳遞風格而消除標籤和goto語句[3],但是他沒有使用「續體」這個名字。
Christopher Strachey、Christopher P. Wadsworth和John C. Reynolds,在指稱語義領域的工作中突出了術語「續體」,此間廣泛利用續體來允許使用函數式編程語義來分析順序程序。
Steve Russell為他給IBM 704的LISP實現的一個用戶,發明了續體來解決雙重遞歸問題[4],但是他沒有為其命名。
頭等續體對一門語言而言是能完全控制指令執行次序的能力。它們可以用來跳轉到產生對當前函數調用的那個函數,或者跳轉到此前已經退出了的函數。人們可以認為頭等續體保存了程序執行狀態,注意到真正的頭等續體不同於進程映像是很重要的,它不保存程序數據,只保存執行上下文。這經常採用「續體三明治」譬喻來說明:
假想你正站在廚房冰箱之前,想要一個三明治。你就在這裡將一個續體放入口袋裡。接着在從冰箱裡拿出火雞肉和麵包自己做了一個三明治,然後坐到桌案前。你啟用了口袋裡的續體,這時發現自己再次站到了冰箱之前,想要一個三明治。幸運的是,在桌案上就有一個三明治,而用於做三明治的材料都沒有了。你可以吃三明治了。[5]
在這個譬喻中,三明治是一部份程序數據,比如在分配堆上一個對象,並非去調用「製做三明治」例程並等它返回,這裡調用「以當前續體製做三明治」例程,它創建一個三明治並在已脫離執行的續體所保存的地方繼續執行。
Scheme是支持續體的第一個完全的生產系統,它最初提供了源自Maclisp的用於例外處理的命名catch
/throw
[6],後來提供了頭等續體算子call-with-current-continuation(簡寫為call/cc
)[7]。
Bruce Duba將callcc
介入到了SML/NJ:
val callcc : ('a cont -> 'a) -> 'a
,這裡的'a cont
是接受類型為'a
的參數的續體的類型;callcc f
應用f
到當前續體,如果f
以參數x
調用這個續體,則如同(callcc f)
返回x
作為結果[8]。
很多編程語言以各種名義體現出頭等續體,特別是:
在支持閉包和適當尾調用的任何編程語言中,都可能以續體傳遞風格書寫程序並手工實現call/cc
。在續體傳遞風格下,call/cc
成為了可以用lambda表達式書寫的一個簡單函數。需要支持適當尾調用,因為在續體傳遞風格下沒有函數會返回,所有調用都是尾調用。
續體被用於計算模型,包括λ演算[19]、指稱語義[20]、演員模型[21]和進程演算。這些模型仰仗於編程者或語義工程師書寫數學函數時採用「續體傳遞風格」(continuation-passing style,簡寫為CPS)[22]。這意味着每個函數都消費一個表示有關於這個函數調用的餘下計算的函數。要返回一個值,這個函數以此返回值調用這個續體函數;要中止這個計算,它返回一個值。以續體傳遞風格書寫程序的函數式編程者,獲得了以任意方式操縱控制流程的表達能力。代價是他們必須手工維護控制和續體的不變條件,這通常是高度複雜的任務。
以續體傳遞風格(CPS)書寫的函數接受一個額外的實際參數:顯式的續體,它是有一個實際參數的函數。當CPS函數已經計算出來其結果值的時候,它通過以這個值作為實際參數調用續體函數來「返回」它。這意味着在調用CPS函數的時候,調用者函數需要提供一個過程接受這個子例程的「返回」值。以這種形式表達代碼使得在直接風格中隱含的一些事物顯露出來。這包括了:過程返回變成了對續體的調用,中間值都有了給定名字,實際參數求值的次序變為顯見,而尾調用成為簡單的將傳遞給這個調用者的續體不修改的傳遞給被調用過程。
CPS的關鍵在於:所有函數都接受稱為其續體的一個額外實際參數,並且在函數調用中的所有實際參數必須要麼是一個變量,要麼是一個λ表達式而非更複雜的表達式。這具有將表達式「從內至外」翻轉的效果,由於表達式最內部份必須首先求值,因此CPS使得求值次序以及控制流程變得明確了。下面是在Scheme語言中僅使用其基本形式的幾個例子,對比了直接風格代碼和對應的CPS代碼,這裡約定了將續體函數表示為名為k
的形式參數:
More information 直接風格, 續體傳遞風格 ...
直接風格 |
續體傳遞風格
|
(define (pyth x y)
(sqrt (+ (* x x) (* y y))))
|
(define (pyth& x y k)
(*& x x (lambda (a)
(*& y y (lambda (b)
(+& a b (lambda (c)
(sqrt& c k))))))))
|
(define (factorial n)
(if (= n 0)
1
(* n (factorial (- n 1)))))
|
(define (factorial& n k)
(=& n 0 (lambda (t)
(if t
(k 1)
(-& n 1 (lambda (n-1)
(factorial& n-1 (lambda (acc)
(*& n acc k)))))))))
|
(define (factorial n)
(define (loop n acc)
(if (= n 0)
acc
(loop (- n 1) (* n acc))))
(loop n 1))
|
(define (factorial& n k)
(define (loop& n acc k)
(=& n 0 (lambda (t)
(if t
(k acc)
(-& n 1 (lambda (n-1)
(*& n acc (lambda (n*acc)
(loop& n-1 n*acc k)))))))))
(loop& n 1 k))
|
Close
注意在CPS版本的代碼中,使用的函數原語(functional primitive)如這裡的*&
、+&
、-&
、=&
和sqrt&
自身也是CPS而非直接風格,所以要使得上述例子在Scheme系統中運行,就必須寫出這些函數原語的CPS版本,例如=&
定義為:
(define (=& x y k)
(k (= x y)))
更通用的方式是寫一個轉換例程:
(define (cps-prim f)
(lambda args
(let ((r (reverse args)))
((car r) (apply f (reverse (cdr r)))))))
(define =& (cps-prim =))
(define *& (cps-prim *))
(define +& (cps-prim +))
(define -& (cps-prim -))
(define sqrt& (cps-prim sqrt))
要在直接風格函數中調用CPS函數,必須提供接收CPS函數計算結果的一個續體。上述例子在所需CPS函數原語均已提供的條件下,可以調用為:
(pyth& 3 4 (lambda (x) x))
(factorial& 4 (lambda (x) x))
一般的編程經常不採用CPS函數原語[23],比如將前面的階乘函數寫為:
(define (factorial& n k)
(if (= n 0)
(k 1)
(factorial&
(- n 1)
(lambda (acc) (k (* n acc))))))
主條目:以當前續體調用
Scheme編程語言包括了控制流程算子call-with-current-continuation(簡寫為call/cc
),Scheme程序可以通過它操縱控制流程。在計算機科學中,使這種類型的隱含程序狀態可見為對象,術語上叫做實化。在Scheme中,續體對象是頭等值並被表示為函數,具有函數應用作為其唯一的運算。Scheme在應用續體和函數二者之間不做語法上的區分。
在Scheme中,出現在一個表達式中的(call/cc f)
,接受一個函數f
作為它的唯一實際參數,並把它應用到這個表達式的當前續體上。當一個續體對象被應用於一個實際參數的時候,現存的續體被消除,而應用的續體在其所在位置上被恢復,所以程序流程將在續體被捕獲的那一點上繼續,而「續體的實際參數」則變成call/cc
調用的「返回值」。call/cc
建立的續體可以被多於一次調用,甚至可以從這個call/cc
應用的動態時效範圍(extent)的外部來調用。
例如在表達式((call/cc f) e)
中,在概念上給出f
所應用到的當前續體的方式,是通過將(call/cc f)
替代為以lambda抽象綁定的一個變量比如叫做c
,故而它的當前續體是(lambda (c) (c e))
。對它應用函數f
,得到最終結果(f (lambda (c) (c e)))
。而在表達式(e (call/cc f))
中,子表達式的(call/cc f)
的續體是(lambda (c) (e c))
,故而整個表達式等價於(f (lambda (c) (e c)))
。
下面的例子中,使用call/cc
來模擬C風格語言中的return語句:
(define (f return)
(return 2)
3)
(f (lambda (x) x))
=> 3
(call/cc f)
=> 2
首先以正規函數實際參數(lambda (x) x)
調用f
,它將綁定到形式參數return
上的這個函數應用於值2
,接着執行下一個表達式返回3
。但是,在f
被傳遞給call/cc
的時候,將綁定了續體的形式參數應用於2
,將程序執行強制為跳轉到調用call/cc
那一點上,並導致call/cc
返回值2
。這個結果接着被頂層續體用display
函數打印出來。
在下面的生成器代碼中,call/cc
被使用了兩處:一處用於生成立即返回續體,而另一處用於遍歷一個成員列表的迭代在暫停時保存恢復位置:
(define (generator-factory lst)
(define (control-state return)
(for-each
(lambda (element)
(set! return (call/cc (lambda (resume-here)
(set! control-state resume-here)
(return element)))))
lst)
(return 'fell-off-the-end))
(define (generator)
(call/cc control-state))
generator)
上述代碼的簡單用例:
(define generate-digit
(generator-factory '(0 1 2)))
(define (display-two-digits)
(display (generate-digit)) (newline)
(display (generate-digit)) (newline))
(display-two-digits) ;; 分两行打印 0 和 1
(display-two-digits) ;; 分两行打印 2 和 fell-off-the-end
在定義變量generate-digit
之時,將其定義為函數generator-factory
應用於列表'(0 1 2)
上而得到的一個閉包generator
。這個generator
被定義為(call/cc control-state)
。
在第一次執行(generate-digit)
之時,執行(call/cc control-state)
,進而執行(control-state return)
,它將用於立即返回的續體綁定到形式參數return
之上;然後開始遍歷列表的元素進行迭代的(for-each (lambda (element) ……) lst)
,在求值(set! return (……))
的第二個實際參數之時,進行每一次迭代步驟(call/cc (lambda (resume-here) ……))
,其中的(set! control-state resume-here)
,將綁定到變量resume-here
上的當前續體,重新綁定到函數名字control-state
之上用於以後恢復執行,最後執行(return element)
返回當前列表元素。
在下一次執行(generate-digit)
之時,(call/cc control-state)
將control-state
所綁定的續體應用當前續體上,從而在迭代的(set! return (call/cc ……))
之處恢復執行,它將傳入的立即返回續體綁定到變量return
上,此後繼續這一次的迭代步驟。在遍歷了列表的元素之後迭代結束,最終執行(return 'fell-off-the-end)
,返回一個約定的文字常量。
call/cc
還可以表達其他複雜的原始運算。下面的代碼使用續體達成協作式多任務的用戶級線程,亦稱為協程:
(define *ready-list* '())
(define (fork fn arg)
(call/cc (lambda (return)
(set! *ready-list* (cons return *ready-list*))
(fn arg)
(let ((cont (car *ready-list*)))
(set! *ready-list* (cdr *ready-list*))
(cont #f)))))
(define (yield)
(call/cc (lambda (return)
(let ((cont (car *ready-list*)))
(set! *ready-list*
(append (cdr *ready-list*) (list return)))
(cont #f)))))
(define (schedule)
(let loop ()
(if (not (null? *ready-list*)) (begin
(call/cc (lambda (return)
(let ((cont (car *ready-list*)))
(set! *ready-list*
(append (cdr *ready-list*) (list return)))
(cont #f))))
(loop)))))
上述代碼的簡單用例[24]:
(import (srfi 28))
(define (do-stuff-n-print str)
(let loop ((n 0))
(if (< n 3) (begin
(display (format "~a ~a~%" str n))
;; 调用退让过程,它捕获调用者的当前续体,
;; 将其追加到等待线程的列表,本线程暂停。
(yield)
(loop (+ n 1))))))
(define (main)
;; 调用分叉过程,它接受一个函数和相应的一个参数,
;; 创建一个新线程运行这个函数。
(fork do-stuff-n-print "This is AAA")
(fork do-stuff-n-print "Hello from BBB")
;; 调用调度过程,它采用FIFO,只要有任何其他线程等待,
;; 就取其中第一个线程运行,最终无等待者时结束。
(schedule))
(main)
其輸出為:
This is AAA 0
Hello from BBB 0
This is AAA 1
Hello from BBB 1
This is AAA 2
Hello from BBB 2
Gerald J. Sussman, Guy L. Steele Jr. Lambda: The Ultimate Imperative. 1976 [2021-11-11]. (原始內容存檔於2022-04-10). 「 Reynolds uses the term "continuation" in [Reynolds 72]. Church clearly understood the use of continuations; it is the only way to get anything accomplished at all in pure lambda calculus! For example, examine his definition of ordered pairs and triads on page 30 of [Church 41]. In SCHEME notation. this is:
[M, N]
means (LAMBDA (A) (A M N))
21
means (LAMBDA (A) (A (LAMBDA (B C) B)))
22
means (LAMBDA (A) (A (LAMBDA (B C) C)))
where 21
e.g. selects the first element of a pair. (Note that these functions are isomorphic to CONS
, CAR
, and CDR
!) 」
Kent M. Pitman. The Revised Maclisp Manual. 1983, 2007 [2021-10-15]. (原始內容存檔於2021-12-21). Historically, (CATCH form)
evolved to handle the fact that programmers were using (ERRSET (...(ERR)...))
to accomplish non-local returns since there was once no other way to get that functionality. CATCH and THROW were introduced so that programmers could write (CATCH (...(THROW val)...))
instead where there was really no error condition. However, it was found that confusion would often result using unlabelled CATCH/THROW because an unlablled CATCH could catch a throw it hadn't intended to. This is why named CATCH was invented.
Gerald J. Sussman, Guy L. Steele Jr.. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. 1975 (英文). CATCH
- This is the "escape operator" which gives the user a handle on the control structure of the interpreter. The expression:
(CATCH <identifier> <expression>)
evaluates <expression>
in an environment where <identifier>
is bound to a continuation which is "just about to return from the CATCH
"; that is, if the continuation is called as a function of one argument, then control proceeds as if the CATCH
expression had returned with the supplied (evaluated) argument as its value. ……
As another example, we can define a THROW
function, which may then be used with CATCH
much as they are in LISP:
(DEFINE THROW (LAMBDA (TAG RESULT) (TAG RESULT)))
C# guide — Task asynchronous programming model — Threads. [2024-01-20]. (原始內容存檔於2024-03-06). Async methods are intended to be non-blocking operations. An await
expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method.
S. B. Wample, R. E. Griswold. Co-expression in Icon*. The Computer Journal, Volume 26, Issue 1, Pages 72–78. 1983.
Coro. [2021-10-11]. (原始內容存檔於2013-08-06).
Michael J. Fischer. Lambda Calculus Schemata. In Proceedings of an ACM Conference on Proving Assertions about Programs (1972) 104–109. 1972. Definition 4.3: A schema p
is safe if for every subformula of the form q = (q0, q1, …, qn)
, either q0∈
or for all i
, 0≤i≤n
, qi
is a lambda-function, a constant or a variable.
Thus, in a safe schema, we prohibit the value returned by an application or conditional from being passed on to another function as an argument. It follows that such a value can never be evaluated and must eventually propagate to the top level to become the final result of the whole evaluation. One can then show:
Lemma 4.2: Let f
be a safe lambda-function. Then fcnd(f) = fcnr(f)
.
We are now in a position to state the main theorem in precise terms.
Theorem I: Let f
be any lambda function. We can effectively find a lambda-function f'
such that fcnd(f') = fcnr(f') = fcnr(f)
for all interpretations.
Proof (sketch): By assumption, f = (λx1…xn.p)
for some schema p
with free variables x1, …, xn
. Using well-known techniques, we can first find a schema p1
such that …… .
We now define a function Φ
to translate p1
into a safe form p2
, and p2
will be such that fcnr((λx1…xn.p)) = fcnd((λx1…xn.(p2 (λx.x))))
. We then take f' = (λx1…xn.(p2 (λx.x)))
. ……
Thus, our idea is to modify the schema so that for any sub-lambda-function g
, instead of g
passing its result back to the caller, the caller is passed to g
as an additional functional argument, g
then applies the new argument to the result it used to return, thereby avoiding the necessity of returning immediately. ……
In the definition of Φ
, we also use the auxiliary function Ψ
which adds the new argument to a lambda-function and translates its body. ……
Examples:
⑴ Φ[x] = (λf.(f x))
, x∈
.
⑵ Φ[(x y)] = (λf.((λf.(f x)) (λh.((λf.(f y)) (λx.(h f x))))))
, x,y∈
.
⑶ Φ[(λx.a)] = (λf.(f (λgx.((λf.(f a)) g))))
, x∈
, a∈
.
⑷ Ψ[(λx.a)] = (λgx.((λf.(f a)) g))
, x∈
, a∈
.
John C. Reynolds. Definitional Interpreters for Higher-Order Programming Languages. 1972 [2024-02-24]. (原始內容存檔於2024-02-24). Now suppose we define an expression to be serious if there is any possibility that its evaluation might not terminate. Then a sufficient condition for order-of-application independence is that a program should contain no serious operands or declaring expressions.
Next, suppose that we can divide the functions which may be applied by our program into serious functions, whose application may sometimes run on forever, and trivial functions, whose application will always terminate. …… Then an expression will only be serious if its evaluation can cause the application of a serious function, and a program will be independent of order-of-application if no operand or declaring expression can cause such an application. ……
As can be seen with a little thought, the condition implies that whenever some function calls a serious function, the calling function must return the same result as the called function, without performing any further computation. But any function which calls a serious function must be serious itself. Thus by induction, as soon as any serious function returns a result, every function must immediately return the same result, which must therefore be the final result of the entire program.
Nevertheless, there is a method for transforming an arbitrary program into one which meets our apparently restrictive condition. …… Basically, one replaces each serious function fold (except the main program) by a new serious function fnew which accepts an additional argument called a continuation. The continuation will be a function itself, and fnew is expected to compute the same result as fold, apply the continuation to this result, and then return the result of the continuation, i.e.,
fnew(x1, …, xn, c) = c(fold(x1, …, xn))
.
This introduction of continuations provides an additional "degree of freedom" which can be used to meet our condition for order-of-evaluation independence. Essentially, instead of performing further actions after a serious function has returned, one imbeds the further actions in the continuation which is passed to the serious function.
Carl Hewitt, Peter Bishop, Irene Greif, Brian Smith, Todd Matson, Richard Steiger. Actor induction and meta-evaluation. 1973 [2022-11-15]. (原始內容存檔於2022-11-15). 「For some years we have been constructing a series of languages to express our evolving understanding of the above semantic issues. The latest of these
is called PLANNER-73. ……
We shall assume that the reader is familiar with advanced pattern matching languages such as SNOBOL4, CONVERT, PLANNER-71, QA4, and POPLER.
We shall use (%A M%)
to indicate sending the message M to the actor A. We shall use [s1 s2 … sn]
to denote the finite squence s1, s2, … sn. ……
Identifiers can be created by the prefix operator =
. ……
An expression (=> pattern body)
is an abbreviation for (receive(message pattern) body)
where receive is a more general actor that is capable of bindinq elements of the action in addition to the message.
Evaluating
(%(=> pattern body) the-message%)
, i.e. sending
(=> pattern body) the-message
,
will attempt to match the-message against pattern, If the-message is not of the form specified by pattern, then the actor is NOT-APPLICABLE
to the-message. If the-message matches pattern, the body is evaluated.
……
Conceptually at least a new actor is created every time a message is sent. Consider sending to a target T a message M and a continuation C.
(send T
(message M
[#continuation C]))
The transmission (%T M%)
is an abbreviation for the above where C is defaulted to be the caller. If target T is the following:
(receive
(message the-body
[#continuation =the-continuation])
the-body)
then the the-body is evaluated in an environment where the-message is bound to M and the-continuation is bound to C.
……
Many actors who are executing in parallel can share the same continuation. They can all send a message [“return”]
to the same continuation. 」
Gerald J. Sussman, Guy L. Steele Jr. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. 1975 (英文). It is always possible, ……to perform any calculation in this way: rather than reducing to its value, it reduces to an application of a continuation to its value (cf. [Fischer]). That is, in this continuation-passing programming style, a function always "returns" its result by "sending" it to another function.
這組代碼也可以選用冗餘但普適的彈跳床語義的調度過程:
(define (schedule)
(let loop ()
(if (not (null? *ready-list*)) (begin
(call/cc (lambda (return)
(let ((cont (car *ready-list*)))
(set! *ready-list*
(cons return (cdr *ready-list*)))
(cont #f))))
(loop)))))
- Peter Landin. A Generalization of Jumps and Labels (PDF). Report. UNIVAC Systems Programming Research. Reprinted in Higher Order and Symbolic Computation, 11(2):125-143, 1998, with a foreword by Hayo Thielecke. August 1965.
- Daniel Bobrow. A Model for Control Structures for Artificial Intelligence Programming Languages (PDF). IJCAI. 1973.
- Carl Hewitt, Peter Bishop , Richard Steiger. A Universal Modular Actor Formalism for Artificial Intelligence (PDF). IJCAI. 1973.
- Christopher Strachey, Christopher P. Wadsworth. Continuations: a Mathematical semantics for handling full jumps. Technical Monograph PRG-11. Oxford University Computing Laboratory. Reprinted in Higher Order and Symbolic Computation, 13(1/2):135—152, 2000, with a foreword by Christopher P. Wadsworth. January 1974.
- John C. Reynolds. Definitional Interpreters for Higher-Order Programming Languages. Proceedings of 25th ACM National Conference, pp. 717–740. Reprinted in Higher-Order and Symbolic Computation 11(4):363-397, 1998, with a foreword. 1972.
- John C. Reynolds. On the Relation between Direct and Continuation Semantics. Proceedings of Second Colloquium on Automata, Languages, and Programming. LNCS Vol. 14, pp. 141–156. 1974.
- Reynolds, John C. The Discoveries of Continuations (PDF). Lisp and Symbolic Computation. 1993, 6 (3/4): 233–248 [2021-10-11]. (原始內容存檔 (PDF)於2022-03-20).
- Michael J. Fischer. Lambda-Calculus Schemata (PDF). LISP AND SYMBOLIC COMPUTATION: An International Journal, 6, 259–288. 1993 [2021-11-10]. (原始內容 (PDF)存檔於2022-03-02).
- Gerald J. Sussman, Guy L. Steele Jr. Scheme: An Interpreter for Extended Lambda Calculus. 維基文庫. AI Memo 349, MIT Artificial Intelligence Laboratory, Cambridge, Massachusetts, December 1975. Reprinted in Higher-Order and Symbolic Computation 11(4):405-439, 1998, with a foreword. 1975 (英文).
- Robert Hieb, R. Kent Dybvig, Carl Bruggeman. Representing Control in the Presence of First-Class Continuations. Proceedings of the ACM SIGPLAN '90 Conference on Programming Language Design and Implementation, pp. 66–77.
- Will Clinger, Anne Hartheimer, Eric Ost. Implementation Strategies for Continuations. Proceedings of the 1988 ACM conference on LISP and Functional Programming, pp. 124–131, 1988. Journal version: Higher-Order and Symbolic Computation, 12(1):7-45, 1999.
- Christian Queinnec. Inverting back the inversion of control or, Continuations versus page-centric programming. SIGPLAN Notices 38(2), pp. 57–64. 2003.