Scheme ê call/cc ê 用法舉例(1)——例外處理

(上尾更新 tī:2019-01-20)

介紹

Scheme 是一種函數式語言,所以無 break、yield 等等 ê 流程控制功能 (flow of control)。毋過,伊有一款功能 koh 較強 ê——用 the̍h 著現此時 ê 繼續(continuation) ê call/cc 來合成 tsē-tsē ê 控制流程。

Siánn-mi̍h 是繼續?

繼續,tī 一寡官話 ê 介紹寫做「續體[sio̍k-thé]」,白話講,著是「tshūn--ê 欲計算 ê」。比如講:

(+ 12 (* 2 (/ 2 5))),對 5 來講,伊 ê 猶未計算 ê 部份(著是繼續),是 (+ 12 (* 2 (/ 2 ?)));對 (* 2 (/ 2 5)) 來講,繼續就是(+ 12 ?),咱人 ē-tàng kā 繼續(事實上會當掠做是)當做函數,kā 以上 ê 表達做 (lambda (x) (+ 12 (* 2 (/ 2 x))))(lambda (x) (+ 12 x))

call-with-current-continuation(call/cc,意思:叫用現此時 ê 繼續),伊會接受一 ê 若 (lambda (cc) 欲算ê部份……) 函數,其中 cc 就是對呼叫 call/cc tsit-ê 點 ê 繼續。咱先看覓按怎用:

(define call/cc call-with-current-continuation) ; 因為名 siūnn 長,用 call/cc 簡略。

(define continuation #f) ; 欲 lia̍h ê 繼續

; 用 set! lia̍h 繼續
(+ 12 (call/cc (lambda (cc) (set! continuation cc) (* 7 8)))) ==> 68

(display continuation); ==> #<continuation>

(continuation 6) ;==> 18 = ((lambda (x) (+ 12 x)) 6)

Tī 遮會當看出,cc 指 ê 就是(lambda (x) (+ 12 x))

call/cc ê 另外特色,就是 tī call/cc 執行 ê lambda 函數內底,若是出現 (cc x)(其中 cc 是繼續),(call/cc (lambda (cc) ...))就會執行到 (cc x) 為止,傳轉來 ê 值是 x,而且 (cc x) 以後 ê buē 執行。例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#lang racket ; 用 Racket

; tī Racket 這逝通毋免加
;(define call/cc call-with-current-continuation)

(define return-value #f)

; 無呼叫繼續
(display "===無呼叫繼續 (cc)===\n")


(set! return-value
(call/cc (lambda (cc)
(display "第一改 print\n")
(display "第二改 print\n")
12)))
(display (format "傳轉來 ê 值: ~a\n\n" return-value))


; 呼叫繼續 (cc)
(display "===呼叫繼續 (cc)===\n")

(set! return-value
(call/cc (lambda (cc)
(display "第一改 print\n")
(cc 20) ; 執行到遮為止,(call/cc ...) ê 值是 20
(display "第二改 print\n") ; 這 buē 印出來
12)))
(display (format "傳轉來 ê 值: ~a\n\n" return-value))

執行結果:

===無呼叫繼續 (cc)===
第一改 print
第二改 print
傳轉來 ê 值: 12

===呼叫繼續 (cc)===
第一改 print
傳轉來 ê 值: 20

用 call/cc 合成例外處理 (exception handling)

通常寫程式為著避免有例外影響程式運作,會另外增加處理例外 ê 部份,就是例外處理。咱人會當用 call/cc 佮巨集做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
;call/cc for exception
(define call/cc call-with-current-continuation)

; 設定例外處理 ê 巨集
(define-syntax try
(syntax-rules (except) ; except 是巨集 ê 關鍵字
; 定義 thrower(擲 exception ê)是 throwing,擲出去 ê exception 內容是exception-content
((_ (thrower throwing exception-content)
trying ... ; 試執行 ê 內容

(except ;標示例外處理 ê 段
exception-handling ... ;處理例外 ê 部份
))

; 擴展 ê 結果
(call/cc
(lambda (end-of-try) ; 拄著 end-of-try tio̍h 離開,mài 執行 except-handling ...
(begin
(define exception-content #f) ; 宣告例外內容
(set! exception-content
(call/cc
; 拄著 (throwing msg),tshūn ê mài 執行,設定exception-content 是 msg,
;跳 kàu exception-handling
(lambda (throwing) trying ...
(end-of-try)) ; 拄著 end-of-try tio̍h 離開
))
exception-handling ...))))))


; 試巨集
(display "==== 試巨集 ===\n")

(try
(thrower throw exception) ; 宣告 throw 提來擲例外,內容是 exception

; tī try 內底定義函數,按呢 tsiah thang throw 例外 tī 函數內底。
(define (a-function)
(display "inside a-function\n")
(throw "throw a exception a-function\n"))

(display "這是第 1 ê 測試\n") ; 先執行
(a-function)

(throw "the exception won't be thrown.")
(display "catch 之後,mā buē 執行")

; 例外處理,顯示 exception ê 內容。
(except
(display exception)))

測試結果:

==== 試巨集 ===
這是第 1 ê 測試
inside a-function
throw a exception a-function

欲按怎了解運作流程 neh?用 debugger 家私 ê macro stepper,通 kā 頂懸原始碼 ê 測試(第 33-49 逝)擴充做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(call/cc
(lambda (end-of-try)
(begin
(define exception #f)
(set! exception
(call/cc
(lambda (throw)
(define (a-function)
(display "inside a-function\n")
(throw "throw a exception a-function\n"))
(display "這是第 1 ê 測試\n")
(a-function)
(throw "the exception won't be thrown.")
(display "catch 之後,mā buē 執行")
(end-of-try))))
(display exception))))

Tī 第 8-10 逝,咱定義一个函數 a-function,其中 tī (throw "throw a exception a-function\n")用 throw 這个繼續擲例外ê內容。

所以執行 tse ê 時,會呼叫 a-function,執行到第 10 逝 ê 時,會拄著頭一个繼續 throw,跳出第 6-15 逝 ê call/cc,mā kā exception 設做 "throw a exception a-function\n"(第 5 逝 (set! exception ...)。最後繼續執行第 16 逝 ê 內容,印出 khǹg tī exception 變數 ê 文字。

若是攏無擲例外會按怎?參考下底 ê 原始碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(try
(thrower throw exception) ; 宣告 throw 提來擲例外,內容是 exception

; tī try 內底定義函數,按呢 tsiah thang throw 例外 tī 函數內底。
(define (a-function)
(display "inside a-function\n")
;(throw "throw a exception a-function\n")) ; mài 執行
)

(display "execute before a-function\n") ; 先執行
(a-function)

;(throw "the exception won't be thrown.") ; mài 執行
(display "executing after a-function")

; 例外處理,顯示 exception ê 內容。無擲例外就 buē 執行
(except
(display exception)))

執行結果變做:

==== 試巨集(mài 擲例外 ê 時) ===
execute before a-function
inside a-function
executing after a-function

用 macro stepper 得著:

1
2
3
4
5
6
7
8
9
10
11
12
13
(call/cc
(lambda (end-of-try)
(begin
(define exception #f)
(set! exception
(call/cc
(lambda (throw)
(define (a-function) (display "inside a-function\n"))
(display "execute before a-function\n")
(a-function)
(display "executing after a-function")
(end-of-try))))
(display exception))))))

因為第 12 逝拄著執行 end-of-try 這个繼續,跳出第 1-13 逝 ê (call/cc ...),所以 buē 執行 (display exception)

毋過這種方法嘛有缺點,除了擲 exception 函數愛定義 tī try 內底,準若有別个 try khǹg tī try 區內頭,而且擲 try ê 函數定義 tī 外口 ê try 內底,伊擲出來ê例外,獨獨外口 ê try 區域 tsiah 通掠著,佮咱人所愛 ê,用內底 ê try 區域接著例外無仝。舉例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
; 外口 ê try
(try
(thrower throw exception)
; 定義函數 a-function tī 外口 ê try
(define (a-function)
(display "this is inner side of a-function\n")
(throw "exception from a-function"))

(display "這是第 1 个測試\n")

; 內底 ê try
(try
(thrower throw exception) ; throw 號名 kah 外口 ê kâng 款
(a-function) ; 執行 a-function 佇內面 ê try
(except
(display (format "exception: ~A, catched by inner except" exception)))) ; 希望內底 ê except 通接受著

(throw "擲例外 tī 內面 ê try 後壁")
(display "這馬執行 tse tī 內面 ê try 後壁")
(except
(display (format "exception: \"~A\", catched by outer except" exception))) ; hōo 外口 ê except 接受
)

輸出結果,kan-ta 外口 ê except 接受例外:

1
2
3
這是第 1 个測試
this is inner side of a-function
exception: "exception from a-function", catched by outer except

我想可能是因為 a-function 內底 ê throw指外口 try 區域 ê throw,tsiah 會按呢。

繼續

參考

* [Lambdas, macros and continuations in Scheme — a tutorial ](https://csl.name/post/lambda-macros-continuations/) by Christian Stigen Larsen