在計算機科學中，續體（，也譯作計算續體、續延、延續性），是對計算機程式的控制狀態的一種抽象表示。續體實化了程式控制狀態。可以理解為，續體是一種資料結構，它表示在程序執行中給定點上的計算過程，所建立的資料結構可以被程式語言訪問，而不是被執行時環境所隱藏掉。這對實現程式語言的某些控制機制，比如例外處理、協程、生成器非常有用。 當前續體 從執行程式碼的角度看來，是可以從程式執行的當前點匯出的續體。續體還被用來提及 頭等續體，它是一種構造，賦予程式語言儲存在任何點上的執行狀態的能力，並在程式中後來的點上可能多次返回到這一點。 歷史 Gerald J Sussman 和 Guy L Steele Jr 在 1976 年論文《Lambda The Ultimate Imperative》中，認定 Alonzo Church 在 1941 年著作《The Calculi of LambdaConversion》裡 ，已經清晰的理解了 續體 的使用，這是用純 λ 演算徹底完成所有事情即邱奇編碼的唯一方式，並舉出其中有序對定義所對應的 Scheme 表示為例 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 eg 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 在奧地利的維也納附近巴登召開的關於 形式語言描述語言 的工作會議上，在關於 ALGOL 60 預處理器的的論文《Recursive Definition of Syntax and Semantics》中，倡導透過將真正（proper）過程變換成續體傳遞風格而消除標籤和 goto 語句，但是他沒有使用 續體 這個名字。 Christopher Strachey、Christopher P Wadsworth 和，在指稱語義領域的工作中突出了術語 續體，此間廣泛利用續體來允許使用函數語言程式設計語義來分析順序程式。 Steve Russell 為他給 IBM 704 的 LISP 實現的一個使用者，發明了續體來解決問題，但是他沒有為其命名。 頭等續體 頭等續體對一門語言而言是能完全控制指令執行次序的能力。它們可以用來跳轉到產生對當前函式呼叫的那個函式，或者跳轉到此前已經退出了的函式。人們可以認為頭等續體儲存了程式執行狀態，注意到真正的頭等續體不同於是很重要的，它不儲存程式資料，只儲存執行上下文。這經常採用 續體三明治 譬喻來說明： 假想你正站在廚房冰箱之前，想要一個三明治。你就在這裡將一個續體放入口袋裡。接著在從冰箱裡拿出火雞肉和麵包自己做了一個三明治，然後坐到桌案前。你啟用了口袋裡的續體，這時發現自己再次站到了冰箱之前，想要一個三明治。幸運的是，在桌案上就有一個三明治，而用於做三明治的材料都沒有了。你可以吃三明治了。 在這個譬喻中，三明治是一部份程式資料，比如在分配堆上一個物件，並非去呼叫 製做三明治 例程並等它返回，這裡呼叫 以當前續體制做三明治 例程，它建立一個三明治並在已脫離執行的續體所儲存的地方繼續執行。 Scheme 是支援續體的第一個完全的生產系統，它最初提供了源自 Maclisp 的用於例外處理的命名 catchthrow，後來提供了頭等續體運算元（簡寫為 callcc）。 Bruce Duba 將 callcc 介入到了 SMLNJ： val callcc a cont a a，這裡的 a cont 是接受型別為 a 的引數的續體的型別；callcc f 應用 f 到當前續體，如果 f 以引數 x 呼叫這個續體，則如同 callcc f 返回 x 作為結果。 程式語言支援 很多程式語言以各種名義體現出頭等續體，特別是： Common Lisp：clcont，還可以使用定製宏 C VBNET：asyncawait Factor：callcc0 和 callcc1 Haskell：在 ControlMonadCont 中的續體單子 Haxe：haxecontinuation Icon Unicon：suspend，coexpression：create 與 運算元 Java：Lightwolf 和 javaflowjavaflow requires bytecode manipulation at runtime or compile time Kotlin：Continuation Rhino JavaScript 引擎：Continuation Parrot：Continuation PMC，對所有控制流程使用 Perl：Coro 和 ContinuityContinuity ：call exp 和 continue aContinuation anyValue Python：PyPy 的_continuationcontinulet Racket：（通常簡寫為 callcc） Ruby：callcc Scala：scalautilcontinuations 提供了 shiftreset Scheme：（通常簡寫為 callcc） Smalltalk：Continuation currentDo，在多數現代 Smalltalk 環境中續體不需要額外的 VM 支援就能實現 新澤西 Standard ML：SMLofNJContcallcc 在支援閉包和適當尾呼叫的任何程式語言中，都可能以續體傳遞風格書寫程式並手工實現 callcc。在續體傳遞風格下，callcc 成為了可以用 lambda 表示式書寫的一個簡單函式。需要支援適當尾呼叫，因為在續體傳遞風格下沒有函式會返回，所有呼叫都是尾呼叫。 續體傳遞風格 續體被用於計算模型，包括 λ 演算 、指稱語義、演員模型 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 PLANNER73 We shall assume that the reader is familiar with advanced pattern matching languages such as SNOBOL4 CONVERT PLANNER71 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 themessage ie sending pattern body themessage will attempt to match themessage against pattern If themessage is not of the form specified by pattern then the actor is NOTAPPLICABLE to themessage If themessage 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 thebody continuation thecontinuation thebody then the thebody is evaluated in an environment where themessage is bound to M and thecontinuation 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 和程序演算。這些模型仰仗於程式設計者或語義工程師書寫數學函式時採用 續體傳遞風格（continuationpassing style，簡寫為 CPS）。這意味著每個函式都消費一個表示有關於這個函式呼叫的餘下計算的函式。要返回一個值，這個函式以此返回值呼叫這個續體函式；要中止這個計算，它返回一個值。以續體傳遞風格書寫程式的函數語言程式設計者，獲得了以任意方式操縱控制流程的表達能力。代價是他們必須手工維護控制和續體的不變條件，這通常是高度複雜的任務。 以續體傳遞風格（CPS）書寫的函式接受一個額外的實際引數：顯式的續體，它是有一個實際引數的函式。當 CPS 函式已經計算出來其結果值的時候，它透過以這個值作為實際引數呼叫續體函式來 返回 它。這意味著在呼叫 CPS 函式的時候，呼叫者函式需要提供一個過程接受這個子例程的 返回 值。以這種形式表達程式碼使得在直接風格中隱含的一些事物顯露出來。這包括了：過程返回變成了對續體的呼叫，中間值都有了給定名字，實際引數求值的次序變為顯見，而尾呼叫成為簡單的將傳遞給這個呼叫者的續體不修改的傳遞給被呼叫過程。 CPS 的關鍵在於：所有函式都接受稱為其續體的一個額外實際引數，並且在函式呼叫中的所有實際引數必須要麼是一個變數，要麼是一個 λ 表示式而非更復雜的表示式。這具有將表示式 從內至外 翻轉的效果，由於表示式最內部份必須首先求值，因此 CPS 使得求值次序以及控制流程變得明確了。下面是在 Scheme 語言中僅使用其基本形式的幾個例子，對比了直接風格程式碼和對應的 CPS 程式碼，這裡約定了將續體函式表示為名為 k 的形式引數： 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 n1 factorial n1 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 n1 n acc lambda nacc loop n1 nacc k loop n 1 k 注意在 CPS 版本的程式碼中，使用的函式原語（functional primitive）如這裡的 、、、 和 sqrt 自身也是 CPS 而非直接風格，所以要使得上述例子在 Scheme 系統中執行，就必須寫出這些函式原語的 CPS 版本，例如 定義為： define x y k k x y 更通用的方式是寫一個轉換例程： define cpsprim f lambda args let r reverse args car r apply f reverse cdr r define cpsprim define cpsprim define cpsprim define cpsprim define sqrt cpsprim 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 程式語言包括了控制流程運算元（簡寫為 callcc），Scheme 程式可以透過它操縱控制流程。在計算機科學中，使這種型別的隱含程式狀態可見為物件，術語上叫做實化。在 Scheme 中，續體物件是頭等值並被表示為函式，具有作為其唯一的運算。Scheme 在應用續體和函式二者之間不做語法上的區分。 在 Scheme 中，出現在一個表示式中的 callcc f，接受一個函式 f 作為它的唯一實際引數，並把它應用到這個表示式的當前續體上。當一個續體物件被應用於一個實際引數的時候，現存的續體被消除，而應用的續體在其所在位置上被恢復，所以程式流程將在續體被捕獲的那一點上繼續，而 續體的實際引數 則變成 callcc 呼叫的 返回值。callcc 建立的續體可以被多於一次呼叫，甚至可以從這個 callcc 應用的動態時效範圍（extent）的外部來呼叫。 例如在表示式 callcc f g 中，在概念上給出 f 所應用到的當前續體的方式，是透過將 callcc f 替代為以 lambda 抽象繫結的一個變數比如叫做 cc，故而它的當前續體是 lambda cc cc g。對它應用函式 f，得到最終結果 f lambda cc cc g。而在表示式 g callcc f 中，子表示式的 callcc f 的續體是 lambda cc g cc，故而整個表示式等價於 f lambda cc g cc。 立即返回 下面的例子中，使用 callcc 來模擬 C 風格語言中的 return 語句： define f return return 2 3 f lambda x x 3 callcc f 2 首先以正規函式實際引數 lambda x x 呼叫 f，它將繫結到形式引數 return 上的這個函式應用於值 2，接著執行下一個表示式返回 3。但是，在 f 被傳遞給 callcc 的時候，將綁定了續體的形式引數應用於 2，將程式執行強制為跳轉到呼叫 callcc 那一點上，並導致 callcc 返回值 2。這個結果接著被頂層續體用 display 函式打印出來。 生成器 在下面的生成器程式碼中，callcc 被使用了兩處：一處用於生成立即返回續體，而另一處用於遍歷一個成員列表的迭代在暫停時儲存恢復位置： define generatorfactory lst define controlstate return foreach lambda element set return callcc lambda resumehere set controlstate resumehere return element lst return fellofftheend define generator callcc controlstate generator 上述程式碼的簡單用例： define generatedigit generatorfactory 0 1 2 define displaytwodigits display generatedigit newline display generatedigit newline displaytwodigits 分兩行列印 0 和 1 displaytwodigits 分兩行列印 2 和 fellofftheend 在定義變數 generatedigit 之時，將其定義為函式 generatorfactory 應用於列表 0 1 2 上而得到的一個閉包 generator。這個 generator 被定義為 callcc controlstate。 在第一次執行 generatedigit 之時，執行 callcc controlstate，進而執行 controlstate return，它將用於立即返回的續體繫結到形式引數 return 之上；然後開始遍歷列表的元素進行迭代的 foreach lambda element lst，在求值 set return 的第二個實際引數之時，進行每一次迭代步驟 callcc lambda resumehere ，其中的 set controlstate resumehere，將繫結到變數 resumehere 上的當前續體，重新繫結到函式名字 controlstate 之上用於以後恢復執行，最後執行 return element 返回當前列表元素。 在下一次執行 generatedigit 之時，callcc controlstate 將 controlstate 所繫結的續體應用當前續體上，從而在迭代的 set return callcc 之處恢復執行，它將傳入的立即返回續體繫結到變數 return 上，此後繼續這一次的迭代步驟。在遍歷了列表的元素之後迭代結束，最終執行 return fellofftheend，返回一個約定的常量。 協程 callcc 還可以表達其他複雜的原始運算。下面的程式碼使用續體達成協作式多工的使用者級執行緒，亦稱為協程： define readylist define fork fn arg callcc lambda return set readylist cons return readylist fn arg let cont car readylist set readylist cdr readylist cont f define yield callcc lambda return let cont car readylist set readylist append cdr readylist list return cont f define schedule let loop if not null readylist begin callcc lambda return let cont car readylist set readylist append cdr readylist list return cont f loop 上述程式碼的簡單用例 這組程式碼也可以選用冗餘但普適的語義的排程過程： define schedule let loop if not null readylist begin callcc lambda return let cont car readylist set readylist cons return cdr readylist cont f loop ： import srfi 28 define dostuffnprint str let loop n 0 if n 3 begin display format a a str n 呼叫退讓過程，它捕獲呼叫者的當前續體， 將其追加到等待執行緒的列表，本執行緒暫停。 yield loop n 1 define main 呼叫分叉過程，它接受一個函式和相應的一個引數， 建立一個新執行緒執行這個函式。 fork dostuffnprint This is AAA fork dostuffnprint 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 參見 引用與註釋 延伸閱讀 外部連結 Continuations for Curmudgeons by Sam Ruby Teach Yourself Scheme in Fixnum Days by Dorai Sitaram features a nice chapter on continuations Continuations and Stackless Python by Christian Tismer Continuation functions and jumps httpokmijorgftpcontinuations by Oleg Kiselyov Haskell Continuation Rhino With Continuations Continuations in pure Java from the RIFE2 web application framework Comparison of generators coroutines and continuations source of the above example Category 控制流程 Category 子程式