iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 4
0
Software Development

每天 Racket 3 分鐘系列 第 16

(display (call/cc (lambda (day-15) (day-15 "天下第一奇招 — Racket 的 Continuation"))))

1. Continuation!你在說啥?

沒錯,這真的是我第一次學到 Continuation 時,心裡的 OS,這啥?

我們先來看看大家怎麼說:

R6RS

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 運算流程所有接下來的進程。

Racket Guide

A continuation is a value that encapsulates a piece of an expression’s evaluation context. [2]

Continuation 就是封裝了 expression 執行時的上下文環境。

TSPL4

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:

cont ast 1

當然,+ 的後頭可以接很多個數字,但我們在此先用兩個。而我們設定的這個 call/cc,正是在第二個參數,像這樣:

cont ast 2

這個最後帶進 esp5 成了 + 的第二個參數,然後順理成章地完成了計算。

我們擴充上面的概念,再來寫一個範例:

(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 與幾個經典的應用。


上一篇
(send day-14 set-title! "Racket 也有物件導向 — Class 與 Object — 2")
下一篇
(call/cc (lambda (day-16) (day-16 "天下第一奇招 — Racket 的 Continuation — 2")))
系列文
每天 Racket 3 分鐘17
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言