(上尾更新 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--ê 欲計算 ê」。比如講:
Tī (+ 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) (define continuation #f) (+ 12 (call/cc (lambda (cc ) (set! continuation cc) (* 7 8 )))) ==> 68 (display continuation) (continuation 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 (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)) (display "===呼叫繼續 (cc)===\n" ) (set! return-value (call/cc (lambda (cc) (display "第一改 print\n" ) (cc 20 ) (display "第二改 print\n" ) 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 (define call/cc call-with-current-continuation) (define-syntax try (syntax-rules (except ) ((_ (thrower throwing exception-content) trying ... (except exception-handling ... )) (call/cc (lambda (end-of-try) (begin (define exception-content #f ) (set! exception-content (call/cc (lambda (throwing) trying ... (end-of-try )) )) exception-handling ...)))))) (display "==== 試巨集 ===\n" ) (try (thrower throw exception) (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ē 執行" ) (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) (define (a-function ) (display "inside a-function\n" ) ) (display "execute before a-function\n" ) (a-function ) (display "executing after a-function" ) (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 (thrower throw exception) (define (a-function ) (display "this is inner side of a-function\n" ) (throw "exception from a-function" )) (display "這是第 1 个測試\n" ) (try (thrower throw exception) (a-function ) (except (display (format "exception: ~A, catched by inner except" exception)))) (throw "擲例外 tī 內面 ê try 後壁" ) (display "這馬執行 tse tī 內面 ê try 後壁" ) (except (display (format "exception: \"~A\", catched by outer except" exception))) )
輸出結果,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