プログラミング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も難しい