續體

在計算機科學中，續體（continuation，也譯作計算續體、續延、延續性），是對計算機程序的控制狀態的一種抽象表示。續體實化了程序控制狀態。可以理解為，續體是一種數據結構，它表示在進程執行中給定點上的計算過程，所建立的數據結構可以被編程語言訪問，而不是被運行時環境所隱藏掉。這對實現編程語言的某些控制機制，比如例外處理、協程、生成器非常有用。
“當前續體”從運行代碼的角度看來，是可以從程序執行的當前點導出的續體。續體還被用來提及“頭等續體”，它是一種構造，賦予編程語言保存在任何點上的執行狀態的能力，並在程序中後來的點上可能多次返回到這一點。

**歷史**
Gerald J. Sussman和Guy L. Steele Jr.在1976年論文《Lambda: The Ultimate Imperative》中，認定Alonzo Church在1941年著作《The Calculi of Lambda-Conversion》裡，已經清晰的理解了“續體”的使用，這是用純λ演算徹底完成所有事情即邱奇編碼的唯一方式，並舉出其中有序對定義所對應的Scheme表示為例
  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!) ”
：
(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)))
在1993年的論文《The Discoveries of Continuations》中給出了發現續體的完整歷史。Reynolds認為續體的最早描述由Adriaan van Wijngaarden在1964年9月作出。Van Wijngaarden在奧地利的維也納附近巴登召開的關於“形式語言描述語言”的IFIP工作會議上，在關於ALGOL 60預處理器的Formulation的論文《Recursive Definition of Syntax and Semantics》中，倡導通過將真正（proper）過程變換成續體傳遞風格而消除標籤和goto語句，但是他沒有使用“續體”這個名字。
Christopher Strachey、Christopher P. Wadsworth和John C. Reynolds，在指稱語義領域的工作中突出了術語“續體”，此間廣泛利用續體來允許使用函數式編程語義來分析順序程序。
Steve Russell為他給IBM 704的LISP實現的一個用戶，發明了續體來解決Double recursion問題，但是他沒有為其命名。

**頭等續體**
頭等續體對程式語言而言是能完全控制指令執行次序的能力。它們可以用來跳轉到產生對當前函數調用的那個函數，或者跳轉到此前已經退出了的函數。人們可以認為頭等體保存了程序執行狀態，注意到真正的頭等續體不同於進程映像是很重要的，它不保存程序數據，只保存執行上下文。這經常採用“續體三明治”譬喻來說明：
假想你正站在廚房冰箱之前，想要一個三明治。你就在這裡將一個續體放入口袋裡。接著在從冰箱裡拿出火雞肉和麵包自己做了一個三明治，然後坐到桌案前。你啟用了口袋裡的續體，這時發現自己再次站到了冰箱之前，想要一個三明治。幸運的是，在桌案上就有一個三明治，而用於做三明治的材料都沒有了。你可以吃三明治了。
在這個譬喻中，三明治是一部份程序數據，比如在分配堆上一個對象，並非去調用“製做三明治”例程並等它返回，這裡調用“以當前續體制做三明治”例程，它創建一個三明治並在已脫離執行的續體所保存的地方繼續執行。
Scheme是支持續體的第一個完全的生產系統，它最初提供了源自Maclisp的用於例外處理的命名catch/throwcite wikisource|wslanguage=en|title=Scheme: An Interpreter for Extended Lambda Calculus|author=Gerald J. Sussman, Guy L. Steele Jr.|year=1975|quote=CATCH － This is the "escape operator" which gives the user a handle on the control structure of the interpreter. The expression:(CATCH  )evaluates  in an environment where  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)))，後來提供了頭等續體算子call-with-current-continuation（簡寫為call/cc）。
Bruce Duba將callcc介入到了SML/NJ：
val callcc : ('a cont -> 'a) -> 'a，這裡的'a cont是接受類型為'a的參數的續體的類型；callcc f應用f到當前續體，如果f以參數x調用這個續體，則如同(callcc f)返回x作為結果。

**編程語言支持**
很多編程語言以各種名義體現出頭等續體，特別是：
*Common Lisp：cl-cont，還可以使用定製宏
*C# / VB.NET：async/await
*Factor：callcc0和callcc1
*Haskell：在Control.Monad.Cont中的續體單子
*Haxe：haxe-continuation
*Icon / Unicon：suspend，coexpression：create與@算子
*Java：Lightwolf和javaflow
*Kotlin：Continuation
*Rhino (JavaScript引擎)：Continuation
*Parrot：Continuation PMC，對所有控制流程使用continuation-passing style
*Perl：Coro和Continuity
*Pico：call(exp())和continue(aContinuation, anyValue)
*Python：PyPy的_continuation.continulet
*Racket：call-with-current-continuation（通常簡寫為call/cc）
*Ruby：callcc
*Scala：scala.util.continuations提供了shift/reset
*Scheme：call-with-current-continuation（通常簡寫為call/cc）
*Smalltalk：Continuation currentDo:，在多數現代Smalltalk環境中續體不需要額外的VM支持就能實現
*新澤西Standard ML：SMLofNJ.Cont.callcc
在支持閉包和適當尾調用的任何編程語言中，都可能以續體傳遞風格書寫程序並手工實現call/cc。在續體傳遞風格下，call/cc成為了可以用lambda表達式書寫的一個簡單函數。需要支持適當尾調用，因為在續體傳遞風格下沒有函數會返回，所有調用都是尾調用。

**續體傳遞風格**
續體被用於計算模型，包括λ演算、指稱語義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.|access-date=2024-02-24|archive-date=2024-02-24|archive-url=https://web.archive.org/web/20240224050552/https://dl.acm.org/doi/10.1145/800194.805852|dead-url=no、演員模型
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. ”
和進程演算。這些模型仰仗於編程者或語義工程師書寫數學函數時採用“續體傳遞風格”（continuation-passing style，簡寫為CPS）。這意味著每個函數都消費一個表示有關於這個函數調用的餘下計算的函數。要返回一個值，這個函數以此返回值調用這個續體函數；要中止這個計算，它返回一個值。以續體傳遞風格書寫程序的函數式編程者，獲得了以任意方式操縱控制流程的表達能力。代價是他們必須手工維護控制和續體的不變條件，這通常是高度複雜的任務。
以續體傳遞風格（CPS）書寫的函數接受一個額外的實際參數：顯式的續體，它是有一個實際參數的函數。當CPS函數已經計算出來其結果值的時候，它通過以這個值作為實際參數調用續體函數來“返回”它。這意味著在調用CPS函數的時候，調用者函數需要提供一個過程接受這個子例程的“返回”值。以這種形式表達代碼使得在直接風格中隱含的一些事物顯露出來。這包括了：過程返回變成了對續體的調用，中間值都有了給定名字，實際參數求值的次序變為顯見，而尾調用成為簡單的將傳遞給這個調用者的續體不修改的傳遞給被調用過程。
CPS的關鍵在於：所有函數都接受稱為其續體的一個額外實際參數，並且在函數調用中的所有實際參數必須要麼是一個變量，要麼是一個λ表達式而非更復雜的表達式。這具有將表達式“從內至外”翻轉的效果，由於表達式最內部份必須首先求值，因此CPS使得求值次序以及控制流程變得明確了。下面是在Scheme語言中僅使用其基本形式的幾個例子，對比了直接風格代碼和對應的CPS代碼，這裡約定了將續體函數表示為名為k的形式參數：
注意在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函數原語，比如將前面的階乘函數寫為：
(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中，續體對象是頭等值並被表示為函數，具有Function application作為其唯一的運算。Scheme在應用續體和函數二者之間不做語法上的區分。
在Scheme中，出現在一個表達式中的(call/cc f)，接受一個函數f作為它的唯一實際參數，並把它應用到這個表達式的當前續體上。當一個續體對象被應用於一個實際參數的時候，現存的續體被消除，而應用的續體在其所在位置上被恢復，所以程序流程將在續體被捕獲的那一點上繼續，而“續體的實際參數”則變成call/cc調用的“返回值”。call/cc建立的續體可以被多於一次調用，甚至可以從這個call/cc應用的動態時效範圍（extent）的外部來調用。
例如在表達式((call/cc f) g)中，在概念上給出f所應用到的當前續體的方式，是通過將(call/cc f)替代為以lambda抽象綁定的一個變量比如叫做cc，故而它的當前續體是(lambda (cc) (cc g))。對它應用函數f，得到最終結果(f (lambda (cc) (cc g)))。而在表達式(g (call/cc f))中，子表達式的(call/cc f)的續體是(lambda (cc) (g cc))，故而整個表達式等價於(f (lambda (cc) (g cc)))。

**立即返回**
下面的例子中，使用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)))))
上述代碼的簡單用例：
(import (srfi 28))
(define (do-stuff-n-print str)
(let loop ((n 0))
(if (
其輸出為：
This is AAA 0
Hello from BBB 0
This is AAA 1
Hello from BBB 1
This is AAA 2
Hello from BBB 2

**參見**
continuation-passing style
*Delimited continuation

**引用與註釋**

**延伸閱讀**
cite web|author=Peter Landin|title=A Generalization of Jumps and Labels|publisher=Report. UNIVAC Systems Programming Research. Reprinted in Higher Order and Symbolic Computation, 11(2):125-143, 1998, with a foreword by Hayo Thielecke. |date=August 1965| url=http://www.math.bas.bg/bantchev/place/iswim/j.pdf
*url=https://www.ijcai.org/Proceedings/73/Papers/027C.pdf
*url=https://www.ijcai.org/Proceedings/73/Papers/027B.pdf
*cite web|author=Christopher Strachey, Christopher P. Wadsworth|title=Continuations: a Mathematical semantics for handling full jumps|publisher=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. |date=January 1974| url=http://www.cs.ox.ac.uk/publications/publication3729-abstract.html
*url=https://surface.syr.edu/lcsmith_other/13/
*url=https://kilthub.cmu.edu/articles/journal_contribution/On_the_Relation_Between_Direct_and_Continuation_Semantics/6608018
*dead-url=no
*publisher=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
*cite web|author=Robert Hieb, R. Kent Dybvig, Carl Bruggeman|title=Representing Control in the Presence of First-Class Continuations|publisher=Proceedings of the ACM SIGPLAN '90 Conference on Programming Language Design and Implementation, pp. 66–77. |url=https://dl.acm.org/doi/10.1145/93548.93554
*url=https://dl.acm.org/doi/10.1145/62678.62692
*url=https://www.semanticscholar.org/paper/Inverting-back-the-inversion-of-control-or%2C-versus-Queinnec/91469a9b9685844b1b5b06593f37d763e3121e10

**外部鏈接**
 Continuations for Curmudgeons date=20210616221232  by Sam Ruby- Teach Yourself Scheme in Fixnum Days date=20230203160644  by Dorai Sitaram features a nice chapter on continuations.- Continuations and Stackless Python date=20220309055112  by Christian Tismer- Continuation, functions and jumps date=20231031113027 - http://okmij.org/ftp/continuations/ date=20211026053006  by Oleg Kiselyov- Haskell Continuation date=20240308084302 - Rhino With Continuations date=20240116071352 - Continuations in pure Java date=20240116071317  from the RIFE2 web application framework-  Comparison of generators, coroutines, and continuations, source of the above example date=20211026052319