沒錯,這真的是我第一次學到 Continuation 時,心裡的 OS,這啥?
我們先來看看大家怎麼說:
Whenever a Scheme expression is evaluated there is a continuation wanting the result of the expression. The continuation represents an entire (default) future for the computation. [1]
當一個 Scheme 的 expression 執行時,便有一個 continuation 會獲取這個 expression 執行的結果。而這個 continuation 便代表了 expression 運算流程所有接下來的進程。
A continuation is a value that encapsulates a piece of an expression’s evaluation context. [2]
Continuation 就是封裝了 expression 執行時的上下文環境。
During the evaluation of a Scheme expression, the implementation must keep track of two things: (1) what to evaluate and (2) what to do with the value. ...
We call "what to do with the value" the continuation of a computation. [3]
當 Scheme 的 expression 執行時,執行平台必須保持追蹤兩件事情:一、要執行的對象;二、執行對象值的操作...而我們稱 「執行對象值的操作」為每次運算的 continuation。
或許可以在腦袋裡想像一下,一個 function 在執行時,或各位在呼叫一個物件的 method 時,執行一半,然後停下來,把控制權交出來給別人,然後在後來的某個時刻裡,又再重新呼叫,繼續執行完。
好,這很像什麼呢?很像作業系統的行程管理。Continuation 強悍之處,在於 Scheme 與 Racket 把它當第一級物件來傳遞,可以很容易地實現這種 preemptive 的行程管理機制,也可以說是一種 coroutine。
我們再看 R6RS 接下來的說明:
The call-with-current-continuation procedure allows Scheme programmers to do that by creating a procedure that reinstates the current continuation.
The call-with-current-continuation procedure accepts a procedure, calls it immediately with an argument that is an escape procedure.
This escape procedure can then be called with an argument that becomes the result of the call to call-with-current-continuation.
這段看不懂沒關係,我們用一個簡單的程式來說明:
(+ 5 (call/cc
(lambda (escape)
(escape 5)))) ;; 10
這個 call-with-current-continuation
(簡寫為 call/cc
) 是一個函式,把它安插在函式執行過程中,可以捕捉到函式執行當下狀態的 continuation。而 call/cc
的參數,是一個 lambda
函式,這個 lambda
也要接受一個參數,這個參數稱 escape procedure ,這個 escape procedure 也可以接受一個參數,作為 call/cc
的回傳。
我們在這個簡單的加法運算裡面,看到了一個簡單的 continuation 的使用,而在很早之前的說明中,我們談到了在 Racket 裡所有的運算都是函式操作,因此可以想像一個這樣的 AST:
當然,+
的後頭可以接很多個數字,但我們在此先用兩個。而我們設定的這個 call/cc
,正是在第二個參數,像這樣:
這個最後帶進 esp
的 5
成了 +
的第二個參數,然後順理成章地完成了計算。
我們擴充上面的概念,再來寫一個範例:
(define cc #f)
(+ 5 (call/cc
(lambda (esp)
(set! cc esp)
(esp 5)))) ;; 10
(cc 10) ;; 15
我們先宣告了一個 cc
,給它什麼值都好,Racket 與 Scheme 是弱型別語言,亦即你後來可以把它指定給其他型別的值。然後我們在 call/cc
裡頭,多加了一段 (set! cc esp)
,把這個 escape procedure 指定給 cc
,接下來跟上面一樣。但我們拿到 cc
後,就有趣了,你可以傳給 cc
任意數字,它會回到剛剛那個 expression 的狀態,重新執行一次 (+ 5 ?)
的結果。這就是 Continuation 的基本概念。
雖然很難理解,可是沒關係,不要卡在這裡,明天我們繼續討論 Racket 的 Continuation 與幾個經典的應用。