エラーをシグナルすることによる通常の効果は、実行されていたコマンドを終了してEmacsエディターのコマンドループに即座にリターンすることです。スペシャルフォームcondition-case
を使用してエラーハンドラーを設定することにより、プログラム内の一部で発生するエラーのをトラップを調整することができます。以下は単純な例です:
(condition-case nil (delete-file filename) (error nil))
これはfilenameという名前のファイルを削除して、任意のエラーをcatch、エラーが発生した場合はnil
をリターンします(このような単純なケースではマクロignore-errors
を使用することもできる。以下を参照のこと)。
condition-case
構文は、insert-file-contents
呼び出しによるファイルオープンの失敗のような、予想できるエラーをトラップするために多用されます。condition-case
構文はユーザーから読み取った式を評価するプログラムのような、完全には予測できないエラーのトラップにも使用されます。
condition-case
の2番目の引数は保護されたフォーム(protected
form)と呼ばれます(上記の例では保護されたフォームはdelete-file
の呼び出し)。このフォームの実行が開始されるとエラーハンドラーが効果をもち、このフォームがリターンすると不活性になります。その間のすべてにおいてエラーハンドラーは効果をもちます。特にこのフォームで呼び出された関数とそのサブルーチン等を実行する間、エラーハンドラーは効果をもちます。厳密にいうと保護されたフォーム自身ではなく、保護されたフォームから呼び出されたLispプリミティブ関数(signal
とerror
を含む)だけがシグナルされるというのは、よいことと言えます。
保護されたフォームの後の引数はハンドラーです。各ハンドラーはそれぞれ、どのエラーを処理するかを指定する1つ以上のコンディション名(シンボル)をリストします。エラーがシグナルされたとき、エラーシンボルはコンディション名のリストも定義します。エラーが共通のコンディション名をもつ場合、そのハンドラーがそのエラーに適用されます。上記の例では1つのハンドラーがあり、それはすべてのエラーをカバーするコンディション名error
を指定しています。
適切なハンドラーの検索は、もっとも最近に設定されたハンドラーから始まり、設定されたすべてのハンドラーをチェックします。したがってネストされたcondition-case
フォームに同じエラー処理がある場合には、内側のハンドラーがそれを処理します。
何らかのcondition-case
によりエラーが処理されると、debug-on-error
でエラーによりデバッガーが呼び出されるようにしていても、通常はデバッガーの実行が抑制されます。
condition-case
で補足されるようなエラーをデバッグできるようにしたいなら、変数debug-on-signal
に非nil
値をセットします。以下のようにコンディション内にdebug
を記述することにより、最初にデバッガーを実行するような特定のハンドラーを指定することもできます:
(condition-case nil (delete-file filename) ((debug error) nil))
ここでのdebug
の効果は、デバッガー呼び出しを抑制するcondition-case
を防ぐことだけです。debug-on-error
とその他のフィルタリングメカニズムがデバッガーを呼び出すように指定されているときだけ、エラーによりデバッガーが呼び出されます。エラーによるデバッガへのエンターを参照してください。
マクロcondition-case-unless-debug
は、そのようなフォームのデバッギングを処理する、別の方法を提供する。このマクロは変数debug-on-error
がnil
、つまり任意のエラーを処理しないようなケース以外は、condition-case
とまったく同様に振る舞う。
特定のハンドラーがそのエラーを処理するとEmacsが判断すると、Emacsは制御をそのハンドラーにreturnします。これを行うために、Emacsはそのとき脱出しつつあるバインディング構成により作成されたすべての変数のバインドを解き、そのとき脱出しつつあるすべてのunwind-protect
フォームを実行します。制御がそのハンドラーに達すると、そのハンドラーのbodyが通常どおり実行されます。
そのハンドラーのbodyを実行した後、condition-case
フォームから実行がreturnされます。保護されたフォームは、そのハンドラーの実行の前に完全にexitしているので、そのハンドラーはそのエラーの位置から実行を再開することはできず、その保護されたフォーム内で作られた変数のバインディングを調べることもできません。ハンドラーが行なえることは、クリーンアップと、処理を進行させることだけです。
エラーのシグナルとハンドルにはthrow
とcatch
(明示的な非ローカル脱出: catch
とthrow
を参照)に類似する点がいくつかありますが、これらは完全に別の機能です。エラーはcatch
でキャッチできず、throw
をエラーハンドラーで処理することはできません(しかし対応するcatch
が存在しないときにthrow
を使用することによりシグナルされるエラーは処理できる)。
このスペシャルフォームはprotected-formの実行を囲い込むエラーハンドラーhandlersを確立する。エラーなしでprotected-formが実行されると、リターンされる値はcondition-case
フォームの値になる(成功ハンドラー不在時。以下参照)。この場合にはcondition-case
は効果をもたない。protected-formの間にエラーが発生すると、condition-case
フォームは違いを生じる。
handlersはそれぞれ、(conditions
body…)
というフォームのリストである。ここでconditionsはハンドルされるエラーコンディション名、またはそのハンドラーの前にデバッガーを実行するためのコンディション名(debug
を含む)。t
というコンディション名はすべてのコンディションにマッチする。bodyはこのハンドラーがエラーを処理するときに実行される1つ以上のLisp式。
(error nil) (arith-error (message "Division by zero")) ((arith-error file-error) (message "Either division by zero or failure to open a file"))
発生するエラーはそれぞれ、それが何の種類のエラーかを記述するエラーシンボル(error
symbol)をもち、これはコンディション名のリストも記述する(エラーシンボルとエラー条件を参照)。Emacsは1つ以上のコンディション名を指定するハンドラーにたいして、すべてのアクティブなcondition-case
フォームを検索する。condition-case
の最も内側のマッチがそのエラーを処理する。condition-case
内では、最初に適合したハンドラーがそのエラーを処理する。
ハンドラーのbodyを実行した後、condition-case
は通常どおりリターンして、ハンドラーのbodyの最後の値をハンドラー全体の値として使用する。
引数varは変数である。protected-formを実行するとき、condition-case
はこの変数をバインドせず、エラーを処理するときだけバインドする。その場合には、varをエラー記述(error
description)にバインドする。これはエラーの詳細を与えるリストである。このエラー記述は(error-symbol
.
data)
というフォームをもつ。ハンドラーは何を行なうか決定するために、このリストを参照することができる。たとえばファイルオープンの失敗にたいするエラーなら、ファイル名がdata(エラー記述の3番目の要素)の2番目の要素になる。
varがnil
なら、それはバインドされた変数がないことを意味する。この場合、エラーシンボルおよび関連するデータは、そのハンドラーでは利用できない。
特殊なケースとしてhandlersのいずれか1つが(:success
body…)
形式のリストの場合がある。ここでbodyはprotected-formがエラーなしで終了した際のリターン値(非nil
の場合)にバインドされたvarとともに実行される。
より外側のレベルのハンドラーにcatchさせるために、condition-case
によりcatchされたシグナルを再度throwする必要がある場合もある。以下はこれを行なう方法である:
(signal (car err) (cdr err))
ここでerr
はエラー記述変数(error description
variable)で、condition-case
の1番目の引数は、再throwしたいエラーコンディション。Definition of signalを参照のこと。
この関数は与えられたエラー記述子(error descriptor)にたいするエラーメッセージ文字列をリターンする。これはそのエラーにたいする通常のエラーメッセージをプリントすることにより、エラーを処理したい場合に有用。Definition of signalを参照のこと。
以下は0除算の結果によるエラーを処理するために、condition-case
を使用する例です。このハンドラーは、(beepなしで)エラーメッセージを表示して、非常に大きい数をリターンします。
(defun safe-divide (dividend divisor)
(condition-case err
;; 保護されたフォーム
(/ dividend divisor)
;; ハンドラー (arith-error ; コンディション ;; このエラーにたいする、通常のメッセージを表示する (message "%s" (error-message-string err)) 1000000))) ⇒ safe-divide
(safe-divide 5 0) -| Arithmetic error: (arith-error) ⇒ 1000000
このハンドラーはコンディション名arith-error
を指定するので、division-by-zero(0除算)エラーだけを処理します。他の種類のエラーは(このcondition-case
によっては)、処理されません。したがって:
(safe-divide nil 3) error→ Wrong type argument: number-or-marker-p, nil
以下はerror
によるエラーを含む、すべての種類のエラーをcatchするcondition-case
です:
(setq baz 34) ⇒ 34
(condition-case err
(if (eq baz 35)
t
;; 関数error
の呼び出し
(error "Rats! The variable %s was %s, not 35" 'baz baz))
;; フォームではないハンドラー
(error (princ (format "The error was: %s" err))
2))
-| The error was: (error "Rats! The variable baz was 34, not 35")
⇒ 2
この構文は、それの実行中に発生する任意のエラーを無視してbodyを実行する。その実行中にエラーがなければ、ignore-errors
はbody内の最後のフォームの値を、それ以外はnil
をリターンする。
以下はこのセクションの最初の例をignore-errors
を使用して記述する例である:
(ignore-errors (delete-file filename))
このマクロはignore-errors
と同様だが、指定した特定のエラーコンディションだけを無視する点が異なる。
(ignore-error end-of-file (read ""))
conditionはエラーコンディションのリストでも可。
このマクロはいわばignore-errors
の穏やかなバージョンである。これはエラーを完全に抑止するのではなく、エラーをメッセージに変換する。これはメッセージのフォーマットに、文字列formatを使用する。formatは"Error:
%S"
のように、単一の‘%’シーケンスを含むこと。エラーをシグナルするとは予測されないが、もし発生した場合は堅牢であるべきようなコードの周囲にwith-demoted-errors
を使用する。このマクロはcondition-case
ではなく、condition-case-unless-debug
を使用することに注意。
一部のエラーをcatchして、フルバックトレースやカレントバッファーのようにエラーは発生した条件に関する情報を記録したいことがあります。残念ながらこの種の情報はcondition-case
のハンドラーでは利用できません。なぜならエラーがシグナルされた場所のダイナミックコンテキストではなく、condition-case
のダイナミックコンテキストでハンドラーは実行されるので、そのハンドラーの実行前にスタックが巻き戻されるからです。このような状況にたいしては、以下のフォームを使用することができます:
このスペシャルフォームはbodyを実行して、エラーなしで実行された場合にはhandler-bind
フォームの値をリターンする。この場合にはhandler-bind
は効果をもたない。
handlersは(conditions
handler)
というフォームを要素としてもつリストであること。ここでconditionsはハンドルされるエラーコンディション名かコンディション名のリスト、handlerは評価することによって関数をリターンするフォームであること。condition-case
の場合と同じようにコンディション名はシンボル。
bodyの実行前にhandler-bind
はすべてのhandlerフォームを評価して、bodyの評価中アクティブになるようにそれらのハンドラーをインストールする。エラーがシグナルされるとEmacsはアクティブなすべてのcondition-case
、1つ以上のコンディション名を指定するハンドラーについてはhandler-bind
フォームを検索する。最内でマッチしたのがhandler-bind
によってインストールされたハンドラーのいずれかなら、エラー記述(error
description)を保持する単一の引数とともにhandler関数を呼び出す。
condition-case
に起こることとは反対に、handlerはエラーが発生したダイナミックコンテキストで呼び出される。これは変数のバインド解除やunwind-protect
のクリーンアップを何も行わずに、すべてのダイナミックバインディングが効力をもったままで実行されることを意味する。例外が1つあり、handler関数の実行中はエラーをシグナルするコード間のすべてのエラーハンドラーとhandler-bind
は一時的にサスペンドされる。これはエラーがシグナルされた際にはEmacsがアクティブなcondition-case
、handler関数内部のhandler-bind
、およびカレントのhandler-bind
の外部だけを検索することを意味する。レキシカルバインドされた変数(レキシカルバインディングを参照)もダイナミックエクステントをもたないので影響を受けないことに注意。
通常のすべての関数と同じようにhandlerは典型的にはthrow
を通じて、通常のようにリターンすることで非ローカルにexitできる。handlerが通常通りリターンした場合には、それはハンドラーがエラーの処理を拒否したことを意味する。この場合にはエラーハンドラーの検索は中断された場所から続行される。
たとえばエラーのシグナル時にカレントであるようなバッファーとともに特定のコード部分を実行した間に発生するすべてのエラーのログを、そのコードの実行に影響を与えずに維持したい場合には、以下のようnできます:
(handler-bind ((error (lambda (err) (push (cons err (current-buffer)) my-log-of-errors)))) body-forms...)
これは内部的にcatchされたものではないエラーだけがbody-forms…にログされる。言い換えるとbody-forms…から“逃げ出した”エラーだけをログする。上記ハンドラーは通常通りリターンするので、これらのエラーが周囲を囲むcondition-case
ハンドラー(またはhandler-bind
ハンドラー)に渡されるのを妨げることはできない。
handler-bind
を用いてエラーを他のエラーに置き換えることもできる。以下のコードではbody-forms…実行中に発生したタイプuser-error
のエラーすべてをプレーンなerror
に変換する:
(handler-bind ((user-error (lambda (err) (signal 'error (cdr err))))) body-forms...)
condition-case
とほとんど同じ結果を得ることができるだろう:
(condition-case err (progn body-forms...) (user-error (signal 'error (cdr err))))
しかしhandler-bind
で新たなエラーを(再)シグナルすると元のエラー由来のダイナミック環境が依然としてアクティブであるという点が異なる。これはたとえばその時点でデバッガにエンターすると、元のエラーがシグナルされたポイントが含まれた完全なバックトレースが表示されることを意味している:
Debugger entered--Lisp error: (error "Oops") signal(error ("Oops")) #f(lambda (err) [t] (signal 'error (cdr err)))((user-error "Oops")) user-error("Oops") ... eval((handler-bind ((user-error (lambda (err) ...