Condition System #1 handler-case と handler-bind の違い
handler-bind
(handler-bind ((CONDITION-TYPE 'HANDLER-FN)) FORM*)
FORM* を実行中に CONDITION-TYPE 型のコンディションが投げられると、HANDLER-FN に引数として投げられたコンディションを与えて呼び出す。
handler-bind がやることはそんだけで、エラーから回復とかさせるのは tagbody と go だとか block と return-from だとかで制御を飛ばす必要がある。飛ばさなかったら HANDLER-FN の戻り値は捨てられて、コンディションがさらに上(?)へ飛んでいく。
最初の式は展開するとこうなる。
(let ((si:*condition-handlers* (cons (list (cons 'CONDITION-TYPE 'HANDLER-FN)) system:*condition-handlers*))) FORM*)
要するに si:*condition-handlers* に指定された CONDITION-TYPE と HANDLER-FN の組み合わせを追加して FORM* を評価する。
FORM* を評価中にエラーを投げると、その場で si:*condition-handlers* の中から投げられたエラーが typep な CONDITION-TYPE を探して、組み合わされた HANDLER-FN にそのエラーを与えて呼び出す。つまりエラーを投げる関数がやってる事はこんな感じ。
;;; この関数はあくまでイメージです (defun error (datum &rest args) (let ((condition ;ホントは引数によって変わる (apply #'make-condition datum args))) (funcall (cdr ;; ホントは si:*condition-handlers* はフラットなリストじゃ ;; ないので単なる find ではない (find condition si:*condition-handlers* :test (lambda (condition type/fn) (typep condition (car type/fn))))) condition)))
だもんで、HANDLER-FN はエラーが投げられたときのダイナミック環境の中で呼び出される。
TODO: もうちょっとかみ砕いてリファレンスに追加
handler-case
(handler-case EXPRESSION (CONDITION-TYPE (ARG) FORM*))
大雑把にいうとこんなコードに展開される。
(block :handler-case (let ((signaled-condition)) (tagbody (handler-bind ((CONDITION-TYPE (lambda (condition) (setq signaled-condition condition) (go :handler)))) (return-from :handler-case EXPRESSION)) :handler (return-from :handler-case (let ((ARG signaled-condition)) FORM*)))))
EXPRESSION で CONDITION-TYPE のエラーが投げられたときに何が起きるかというと、handler-bind が指定してる HANDLER-FN(lambda 式の関数)にそのエラーを渡して呼び出す。この関数は (go :handler) で制御を飛ばす。
んで :handler から実行は進んで let 式の中で FORM* を実行した結果を return-from で :handler-case、つまり handler-case 式の戻り値として返す。
両者の違い
まず handler-bind はエラーの伝搬を止めたりしないが、handler-case は止めてくれる。
それと handler-case でエラー処理をする FORM* は handler-bind の HANDLER-FN とは別物。handler-case の FORM* はエラーが投げられたときのダイナミック環境から go で脱出してから実行されるので、その環境を使うことができない。
このせいで再起動を使うには handler-bind を使わないといけない。くっそう。