コンディションを捕捉する


Tag: 【募集中】

コンディションの補足には handler-bind を用います。

  (defun parent ()
    (handler-bind ((error (lambda (c)
                        ...ハンドリング...)))
     ;; body
     (child1)
     (child2)
     (child3)))

handler-bind の body 中でエラーが発生すると、 発生したエラーコンディションを引数 c として ハンドラ関数 が呼び出されます。

ハンドラ関数のうち、コンディションの型が合致しているものだけが呼び出されます。 そのため、例えば上の child1 が warning を発生した場合、用意されているハンドラが error 用ハンドラだけなのでそれは補足されません。(warningとerrorは別のクラス。)

ハンドリングをうまく使うには

さて、ここからがコンディションシステムと他の言語のエラーハンドリングとの違いです。 多少、コンディションシステムの仕組みの詳細に踏み込んでいますが、すこし頑張ってください。 大切なことは、エラーを補足した時点ではまだスタックは巻き戻っていないということです。 child2 が error をシグナル(発生)し、parent の hanlder がこれを補足 (handle) したとしましょう。このとき、コールスタックは以下のようになっています。

  parent > child2 > error > (lambda (c) ...ハンドリング...)

このスタックを巻き戻すためには、 go, return-from, throw のうちどれかを用いて (lambda (c) ) の外にコードを移動させる必要があります。この操作を「非局所脱出」と呼びます。

例:

(tagbody
   start
   (handler-bind ((error (lambda (c) (go start)))
      (child1))
   finish)

このコードは、child1の中でerrorが発生する限り、強制的にスタックを巻き戻して startからやり直させます。もしchildが常にエラーを発生させる場合、無限ループになります。

例2:

(block my-block
   (handler-bind ((error (lambda (c) (return-from my-block 0)))
      (child1)
      (return-from my-block 1)))

このコードは、child1 がエラーを投げた場合、スタックを巻き戻して my-block ブロックから値 0 を返します。エラーを投げない場合、1をブロックから返します。

このような非局所脱出を行わない場合、どうなるでしょうか? 非局所脱出が行われない場合を、「ハンドラがハンドリングの責務を decline (辞退)した」と呼びます。この場合、ハンドラ関数は 実行されます が、スタックは巻き戻されず、その後同じコンディションがより上位のhandler-bind に渡されます。なお、すべての handler-bind が辞退した場合に備えて、最上位のハンドラがいます。それがデバッガです。

handler-case

handler-case は、機能を一言で言えば、上の 例2 を簡単に書けるようにしたマクロです。 私個人としてはあんまり好きではありません。

(handler-case (child1)
  (error (c)
    ...ハンドラ...))

コンディションシステムを、通常のcase文のように使おうという意図があるようです。 そのため、 :no-error という節を追加することができます。

(handler-case (child1)
  (error (c)
    ...ハンドラ...)
  (warning (c)
    ...ハンドラ...)
  (:no-error (c)
    ...ハンドラ...))