GNU Emacs 32.0.50 (9008bf64c3) のマニュアルを PLaMo 翻訳で機械翻訳したものです。誤訳を含むことがあります。原文: https://www.gnu.org/software/emacs/manual/html_node/elisp/Handling-Errors.html ・ emacs-doc.conao3.com

11.7.3.3 エラーを処理するコードの書き方

エラーを通知した際の通常の動作は、実行中のコマンドを終了し、直ちにEmacsエディタのコマンドループに戻ることです。特殊なフォーム condition-case を用いてエラーハンドラを定義することで、プログラムの一部で発生したエラーを捕捉するように設定できます。簡単な例は次のようになります。

(condition-case nil
    (delete-file filename)
  (error nil))

これにより filename という名前のファイルが削除され、エラーが発生した場合はそのエラーを捕捉して nil を返します。(このような単純なケースではマクロ ignore-errors を使用できます。詳細は以下を参照してください。)

condition-case 構文は、insert-file-contents の呼び出しでファイルが開けないといった予測可能なエラーを捕捉するためによく用いられます。また、プログラムがユーザーから読み込んだ式を評価する場合など、全く予測不可能なエラーを捕捉するためにも用いられます。

condition-case の第2引数は 保護形式 と呼ばれます。(上記の例では、保護形式は delete-file の呼び出しです。)エラーハンドラは、この形式の実行が始まると有効になり、この形式が復帰すると無効になります。その間はずっと有効なままとなります。特に、この形式で呼び出された関数やそのサブルーチンなどで実行される間も有効です。これは好ましいことです。というのも、厳密に言えば、エラーを通知できるのは保護形式によって呼び出された Lisp プリミティブ(signal や error を含む)のみであり、保護形式自体では通知できないからです。

保護形式に続く引数はハンドラです。各ハンドラは、どのエラーを処理するかを指定するために、1つ以上の条件名(シンボル)を列挙します。エラーが通知される際に指定されるエラーシンボルもまた、条件名のリストを定義します。ハンドラとエラーに共通の条件名がある場合、そのハンドラはそのエラーに適用されます。上記の例ではハンドラが1つあり、すべてのエラーをカバーする1つの条件名 error が指定されています。

適用可能なハンドラの探索では、直近に設定されたものから順に、設定済みのすべてのハンドラがチェックされます。したがって、入れ子になった2つの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 var protected-form handlers…

マクロ condition-case-unless-debug は、こうした形式のデバッグを処理する別の手段を提供します。これは condition-case と全く同様に動作しますが、変数 debug-on-error が nil でない場合は、適用可能なハンドラを実行する前に Emacs をデバッガに移行させます。(適用可能なハンドラがある場合は、デバッガが終了した後に引き続き実行されます。)

Emacsがあるハンドラがエラーを処理すると判断すると、制御をそのハンドラに返します。そのために、Emacsは終了処理中のバインディング構文によって作成されたすべての変数バインディングを解除し、終了処理中のすべてのunwind-protectフォームのクリーンアップを実行します。制御がハンドラに到達すると、ハンドラの本体が通常通り実行されます。

ハンドラ本体の実行後、処理は condition-case フォームから戻ります。保護されたフォームはハンドラの実行前に完全に終了するため、ハンドラはエラーが発生した箇所から実行を再開することも、保護されたフォーム内で行われた変数束縛を調べることもできません。できるのは、後処理を行って処理を続行することだけです。

エラーの通知と処理は、throw や catch といくらか似ていますが(明示的な非ローカル脱出: catch と throw を参照)、これらは完全に別個の機能です。エラーを catch で捕捉することはできず、また throw をエラーハンドラで処理することもできません(ただし、適切な catch が存在しない場合に throw を使用すると、処理可能なエラーが通知されます)。

特殊形式: 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"))

発生する各エラーには、それがどのような種類のエラーであるかを示すエラーシンボルがあり、そこには条件名の一覧も記載されています(エラーシンボルと条件名を参照)。Emacsは、すべてのアクティブなcondition-caseフォームの中から、これらの条件名の1つ以上を指定するハンドラを検索し、最も内側にある一致するcondition-caseがエラーを処理します。このcondition-case内では、最初に見つかった適用可能なハンドラがエラーを処理します。

ハンドラの本体を実行した後、condition-case はハンドラ本体内の最後のフォームの値を全体の値として、正常に返されます。

引数 var は変数です。condition-case は、protected-form の実行時にはこの変数をバインドせず、エラーを処理する際にのみバインドします。その際、var をローカルに エラー記述子(エラー説明とも呼ばれます)にバインドします。エラー記述子とは、エラーの詳細を示すリストのことです。エラー記述子は (error-symbol . data) という形式をとります。ハンドラはこのリストを参照して、どのような処理を行うかを決定できます。例えば、ファイルのオープン失敗によるエラーの場合、ファイル名は data(エラー記述子の第3要素)の第2要素となります。

var が nil である場合、変数がバインドされていないことを意味します。この場合、エラーシンボルおよび関連データはハンドラから利用できません。

特殊なケースとして、handlers のうちの 1 つが (:success body…) 形式のリストである場合があり、このとき body は、当該式がエラーなく終了した際に protected-form の戻り値に var(nil でない場合)がバインドされた状態で実行されます。

condition-case でキャッチしたシグナルを、外側のハンドラでキャッチできるように再送する必要がある場合があります。その方法は以下の通りです。

  (signal err)

ここで err はエラー記述子変数、condition-case の第1引数は、エラー条件を再スローしたい対象です。シグナルの定義を参照してください。

関数: error-type error

この関数は、エラー記述子 error のエラーシンボルを返します。

関数: error-slot-value error pos

この関数は、エラー記述子 error のフィールド番号 pos に対応する値を返します。フィールド番号は 1 から始まります。例えば、型 wrong-type-argument のエラーの場合、(error-slot-value error 2) は型テストに失敗したオブジェクトを、(error-slot-value error 1) は失敗した述語を返します。

関数: error-message-string error-descriptor

この関数は、指定されたエラー記述子に対応するエラーメッセージ文字列を返します。そのエラーに対する通常のエラーメッセージを出力して処理したい場合に有用です。シグナルの定義を参照してください。

以下に、ゼロ除算で発生するエラーを処理するために condition-case を使用する例を示します。ハンドラはエラーメッセージを表示し(ただしビープ音は鳴らしません)、その後、非常に大きな数値を返します。

(defun safe-divide (dividend divisor)
  (condition-case err
      ;; Protected form.
      (/ dividend divisor)
    ;; The handler.
    (arith-error                        ; Condition.
     ;; Display the usual message for this error.
     (message "%s" (error-message-string err))
     1000000)))
⇒ safe-divide

(safe-divide 5 0)
     ⊣ Arithmetic error: (arith-error)
⇒ 1000000

ハンドラは、ゼロ除算エラーのみを処理するように条件名 arith-error を指定しています。その他の種類のエラーは(この condition-case では)処理されません。したがって:

(safe-divide nil 3)
     error→ Wrong type argument: number-or-marker-p, nil

error によるものも含め、あらゆる種類のエラーを捕捉する condition-case がこちらです:

(setq baz 34)
     ⇒ 34

(condition-case err
    (if (eq baz 35)
        t
      ;; This is a call to the function error.
      (error "Rats!  The variable %s was %s, not 35" 'baz baz))
  ;; This is the handler; it is not a form.
  (error (princ (format "The error was: %s" err))
         2))
⊣ The error was: (error "Rats!  The variable baz was 34, not 35")
⇒ 2
マクロ: ignore-errors body…

この構文は body を実行し、その実行中に発生したエラーは無視します。実行中にエラーが発生しなかった場合、ignore-errors は body の最後のフォームの値を返し、それ以外の場合は nil を返します。

この小節の冒頭にある例をignore-errorsを用いて書き直したものがこちらです:

  (ignore-errors
   (delete-file filename))
マクロ: ignore-error condition body…

このマクロはignore-errorsと同様ですが、指定した特定のエラー条件のみを無視します。

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

condition はエラー条件のリストである場合もあります。

マクロ: with-demoted-errors format body…

このマクロは、ignore-errors のマイルド版のようなものです。エラーを完全に抑制するのではなく、メッセージに変換します。メッセージの書式設定には文字列 format を使用します。format には単一の ‘%’ シーケンス(例:"Error: %S")を指定します。エラーを通知する想定はないが、万が一エラーが発生した場合でも堅牢であるべきコードを囲む際は、with-demoted-errors を使用してください。なお、このマクロでは condition-case ではなく condition-case-unless-debug が使用されています。

場合によっては、エラーを捕捉し、完全なバックトレースや現在のバッファなど、エラーが発生した際の状況に関する情報を記録したいことがあります。残念ながら、condition-case のハンドラではこうした情報は取得できません。というのも、ハンドラが実行される前にスタックが巻き戻されるため、ハンドラはエラーが通知された場所ではなく、condition-case の動的コンテキストで実行されるからです。このような状況では、以下の形式を使用できます。

マクロ: handler-bind handlers body…

この特殊フォームはbodyを実行し、エラーなく実行された場合、返される値がhandler-bindフォームの値となります。この場合、handler-bindは影響を及ぼしません。

handlers は、(conditions handler) 形式の要素のリスト(ここで conditions は処理対象となるエラー条件名、または条件名のリスト)である必要があり、handler は、その評価結果が関数となる形式である必要があります。condition-case と同様に、条件名はシンボルです。

body を実行する前に、handler-bind はすべての handler フォームを評価し、body の評価中に有効となるようそれらのハンドラをインストールします。エラーが通知されると、Emacs はアクティブなすべての condition-case および handler-bind フォームの中から、これらの条件名の1つ以上を指定しているハンドラを検索します。最も内側にある一致するハンドラが handler-bind によってインストールされたものである場合、エラー記述子を保持する単一の引数とともに handler 関数が呼び出されます。

condition-case の場合とは異なり、handler はエラーが発生した動的コンテキストで呼び出されます。つまり、変数バインディングの解除や unwind-protect のクリーンアップ処理が行われることなく実行されるため、それらすべての動的バインディングが引き続き有効な状態となります。ただし、1点例外があります。handler 関数の実行中は、エラーを通知したコードから handler-bind までの間にあるすべてのエラーハンドラが一時的に中断されます。そのため、エラーが通知された際、Emacs は handler 関数内または現在の handler-bind の外にあるアクティブな condition-case および handler-bind フォームのみを検索します。また、字句的にバインドされた変数(字句的バインディング を参照)は動的なスコープを持たないため、影響を受けないことに注意してください。

通常の関数と同様に、handler は(通常は throw を介して)非局所的に抜けるか、あるいは正常に復帰することができます。handler が正常に復帰した場合、それはハンドラがエラー処理を拒否したことを意味し、エラーハンドラの探索は中断された箇所から再開されます。

例えば、特定のコードの実行中に発生したすべてのエラーと、エラーが通知された時点でのバッファをログに記録しつつ、そのコードの動作にはそれ以外の影響を与えたくない場合、次のように記述できます。

(handler-bind
    ((error
      (lambda (err)
        (push (cons err (current-buffer)) my-log-of-errors))))
  body-forms...)

これにより、内部で捕捉されなかったエラー、つまり 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) ...