11.7.3.3 エラーを処理するコードの記述

エラーをシグナルすることによる通常の効果は、実行されていたコマンドを終了して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プリミティブ関数(signalerrorを含む)だけがシグナルされるというのは、よいことと言えます。

保護されたフォームの後の引数はハンドラーです。各ハンドラーはそれぞれ、どのエラーを処理するかを指定する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とその他のフィルタリングメカニズムがデバッガーを呼び出すように指定されているときだけ、エラーによりデバッガーが呼び出されます。エラーによるデバッガへのエンターを参照してください。

Macro: condition-case-unless-debug var protected-form handlers…

マクロcondition-case-unless-debugは、そのようなフォームのデバッギングを処理する、別の方法を提供する。このマクロは変数debug-on-errornil、つまり任意のエラーを処理しないようなケース以外は、condition-caseとまったく同様に振る舞う。

特定のハンドラーがそのエラーを処理するとEmacsが判断すると、Emacsは制御をそのハンドラーにreturnします。これを行うために、Emacsはそのとき脱出しつつあるバインディング構成により作成されたすべての変数のバインドを解き、そのとき脱出しつつあるすべてのunwind-protectフォームを実行します。制御がそのハンドラーに達すると、そのハンドラーのbodyが通常どおり実行されます。

そのハンドラーのbodyを実行した後、condition-caseフォームから実行がreturnされます。保護されたフォームは、そのハンドラーの実行の前に完全にexitしているので、そのハンドラーはそのエラーの位置から実行を再開することはできず、その保護されたフォーム内で作られた変数のバインディングを調べることもできません。ハンドラーが行なえることは、クリーンアップと、処理を進行させることだけです。

エラーのシグナルとハンドルにはthrowcatch (明示的な非ローカル脱出: catchthrowを参照)に類似する点がいくつかありますが、これらは完全に別の機能です。エラーはcatchでキャッチできず、throwをエラーハンドラーで処理することはできません(しかし対応するcatchが存在しないときにthrowを使用することによりシグナルされるエラーは処理できる)。

Special Form: condition-case var protected-form handlers…

このスペシャルフォームは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番目の要素になる。

varnilなら、それはバインドされた変数がないことを意味する。この場合、エラーシンボルおよび関連するデータは、そのハンドラーでは利用できない。

特殊なケースとしてhandlersのいずれか1つが(:success body…)形式のリストの場合がある。ここでbodyprotected-formがエラーなしで終了した際のリターン値(非nilの場合)にバインドされたvarとともに実行される。

より外側のレベルのハンドラーにcatchさせるために、condition-caseによりcatchされたシグナルを再度throwする必要がある場合もある。以下はこれを行なう方法である:

  (signal (car err) (cdr err))

ここでerrはエラー記述変数(error description variable)で、condition-caseの1番目の引数は、再throwしたいエラーコンディション。Definition of signalを参照のこと。

Function: error-message-string error-descriptor

この関数は与えられたエラー記述子(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
Macro: ignore-errors body…

この構文は、それの実行中に発生する任意のエラーを無視してbodyを実行する。その実行中にエラーがなければ、ignore-errorsbody内の最後のフォームの値を、それ以外はnilをリターンする。

以下はこのセクションの最初の例をignore-errorsを使用して記述する例である:

  (ignore-errors
   (delete-file filename))
Macro: ignore-error condition body…

このマクロはignore-errorsと同様だが、指定した特定のエラーコンディションだけを無視する点が異なる。

  (ignore-error end-of-file
    (read ""))

conditionはエラーコンディションのリストでも可。

Macro: with-demoted-errors format body…

このマクロはいわばignore-errorsの穏やかなバージョンである。これはエラーを完全に抑止するのではなく、エラーをメッセージに変換する。これはメッセージのフォーマットに、文字列formatを使用する。format"Error: %S"のように、単一の‘%’シーケンスを含むこと。エラーをシグナルするとは予測されないが、もし発生した場合は堅牢であるべきようなコードの周囲にwith-demoted-errorsを使用する。このマクロはcondition-caseではなく、condition-case-unless-debugを使用することに注意。

一部のエラーをcatchして、フルバックトレースやカレントバッファーのようにエラーは発生した条件に関する情報を記録したいことがあります。残念ながらこの種の情報はcondition-caseのハンドラーでは利用できません。なぜならエラーがシグナルされた場所のダイナミックコンテキストではなく、condition-caseのダイナミックコンテキストでハンドラーは実行されるので、そのハンドラーの実行前にスタックが巻き戻されるからです。このような状況にたいしては、以下のフォームを使用することができます:

Macro: handler-bind handlers body…

このスペシャルフォームは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-casehandler関数内部の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) ...

This page has generated for branch:work/emacs-30_69b16e5c63840479270d32f58daea923fe725b90, commit:5e3f74b56ff47b5bcef2526c70f53f749bbd45f6 to check Japanese translation.