プログラミングGauche 19.7 簡易な例外機構のまとめ
マクロとcall/cc
を使ったcatch
とthrow
の実装の解説を備忘録として残します.
;; 簡易例外機構 (define *signals* '()) (define-syntax catch (syntax-rules (finally) [(_ (sig body ...) (finally follow ...)) (let* ((signals-backup *signals*) (val (call/cc (lambda (k) (set! *signals* (cons (cons 'sig k) *signals*)) body ...)))) (set! *signals* signals-backup) follow ... val)] [(_ (sig body ...)) (catch (sig body ...) (finally))])) (define-syntax throw (syntax-rules () [(_ sig val) ((cdr (assq 'sig *signals*)) val)]))
;; サンプルコード (define (div n d) (if (= d 0) (throw DivedeZeroError (print #`"ERROR: Divide Zero Error Occured...\n divide ,n by ZERO!\n--------------------")) (/ n d))) (define (percentage a b) (catch (DivedeZeroError (print (* (div a b) 100.0) "%")) (finally (print "follow ..."))))
処理の流れを説明します.
*signal*
の初期値をsignals-backup
に束縛します.
その後*signal*
には(set! *signal* ...)
の行でcar
にシグナル,cdr
に継続を渡すk,というペアのリストが束縛されます.
((sig . k))
という形になります.
サンプルコードでは((DivisedZeroError . k))
が入ります.
その後body
が実行されます.
サンプルコードではpercentage
の最初のprint
行.
ここでdiv
が呼ばれます.
(= d 0)
のが真の時にthrow
が呼ばれます.
(cdr (assq 'sig *signals*))
が評価され継続を表すk
が返され,(k val)
でthrow
のprint
行を実行し,戻り値がcatch
のvalに束縛されます.
(※ twitterでkeenさん(@blackenedgold)さんから,継続を渡すk
ではなく,継続を表すk
と教えていただいたので訂正しました.)
ここではまってたのですが,(= d 0)
なのにシグナルが違っていた場合は(cdr #f)
となりエラーが返ります.
シグナルを間違えるなって話ですね.
(= d 0)
が#f
の時は(/ n d)
が実行され値が返ります.
catch
に返ってくると*signals*
にシグナルの初期値を戻します.
そしてfinally ...
を実行します.
最後にvalに束縛した値を返して終了です.
ここではpercentage
はprint
してるので#<undef>
が返ります.
こうして一つ一つ追えばそんなに難しくないですね.
でも理解するのに時間かかりました...
最後に実行例紹介して終わります.
gosh> (percentage 1 40) 2.5% follow ... #<undef> gosh> (percentage 10 0) ERROR: Divide Zero Error Occured... divide 10 by ZERO! -------------------- follow ... #<undef>
マクロもcall/ccも難しい