[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
Lispプログラムは、式、すなわち、フォーム(forms、see 節 8.2 フォームの種類)から 成ります。 フォームを制御構造(control structures)で囲むことで、 フォームの実行順序を制御します。 制御構造はスペシャルフォームであり、 その内側にあるフォームの実行をいつ行うか、行わないか、 何回行うかを制御します。
もっとも単純な実行順序は逐次実行です。 最初のフォームaを実行し、 それからつぎのフォームbを実行し、といった具合です。 関数の本体やLispコードのファイルのトップレベルに複数のフォームを順に書くと、 このようになります。 つまり、書かれている順番にフォームを実行します。 これをテキスト上の順序(textual order)と呼びます。 たとえば、関数本体が2つのフォームaとbから成る場合、 関数を評価すると、まずaを評価し、つぎにbを評価して、 関数の値はbの値になります。
明示的な制御構造により、逐次実行以外の実行順序が可能になります。
Emacs Lispには数種類の制御構造があり、 逐次実行の変形、条件付き実行、繰り返し実行、 (制御された)ジャンプなどです。 これらすべては、以下に説明します。 組み込みの制御構造はスペシャルフォームです。 というのは、それらのサブフォームは必ずしも評価しませんし、 逐次評価するわけでもないからです。 マクロを使えば、独自の制御構造の構文を定義できます(see 節 12. マクロ)。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
現れる順番にフォームを評価することは、 1つのフォームから別のフォームへ制御を移すもっとも一般的な方法です。 関数本体などのある種の文脈では、自動的にこのようになります。 それ以外では、これを行う制御構造の構文を使う必要があります。 progn
がその制御構造で、Lispのもっとも単純な制御構造です。
スペシャルフォームprogn
はつぎのような形です。
(progn a b c ...) |
これは、フォーム、a、b、c、…をこの順に評価します。 これらのフォームをprogn
フォームの本体と呼びます。 本体の最後のフォームの値が、progn
全体の値になります。
初期のころのLispでは、progn
は、 2つ以上のフォームを逐次実行しそれらの最後の値を使う唯一の方法でした。 しかし、プログラマは、(当時は)1つのフォームしか許されていない 関数の本体では、 progn
を使う必要がしばしばあることに気づきました。 そのため、関数本体を『暗黙のprogn
』にしたのです。 つまり、実際のprogn
の本体のように、 複数のフォームを許すようにしたのです。 多くの他の制御構造も、同様に、暗黙のprogn
です。 その結果、progn
は、かつてほどは多用されません。 現在では、unwind-protect
、and
、or
の内側や、 if
のthen部分で必要とされるのがほとんどです。
(progn (print "The first form") (print "The second form") (print "The third form")) -| "The first form" -| "The second form" -| "The third form" => "The third form" |
他の2つの制御構造も同様にフォームを逐次評価しますが、 返す値が異なります。
(prog1 (print "The first form") (print "The second form") (print "The third form")) -| "The first form" -| "The second form" -| "The third form" => "The first form" |
変数のリストから先頭要素を取り除き、取り除いた要素を返すにはつぎのように書く。
(prog1 (car x) (setq x (cdr x))) |
(prog2 (print "The first form") (print "The second form") (print "The third form")) -| "The first form" -| "The second form" -| "The third form" => "The second form" |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
条件付き制御構造は、選択肢を選びます。 Emacs Lispには、4つの条件付きフォームがあります。 他の言語のものとほとんど同じif
、 if
の変形であるwhen
やunless
、 一般化したcase文であるcond
です。
if
は、conditionをもとにして、 then-formかelse-formsを選ぶ。 conditionがnil
以外に評価されると、 then-formを評価し、その結果を返す。 さもなければ、else-formsをテキスト上の順に評価し、 その最後のものの値を返す。 (if
のelse部分は、暗黙のprogn
の例である。 see 節 9.1 逐次実行。)
conditionが値nil
であり、かつ、else-formsがないと、 if
はnil
を返す。
if
がスペシャルフォームであるのは、 選択しなかった分岐をけっして評価しないからである。 したがって、つぎの例では、 print
はけっして呼ばれないためtrue
は表示されない。
(if nil (print 'true) 'very-false) => very-false |
if
の変形であり、else-formsがなく、 then-formsは複数のフォームでもよい。 特に、
(when condition a b c) |
は、つぎとまったく等価である。
(if condition (progn a b c) nil) |
if
の変形である。
(unless condition a b c) |
は、つぎとまったく等価である。
(if condition nil a b c) |
cond
は任意個数の選択肢から1つを選ぶ。 cond
の各節clauseはリストである必要がある。 このリストのCARがcondition(条件)である。 残りの要素は、あれば、body-forms(本体フォーム)である。 つまり、各節はつぎのようになる。
(condition body-forms...) |
cond
は、各節のconditionを評価して、 各節をテキスト上の順に試す。 conditionの値がnil
以外であれば、 その節は『成功』する。 そうすると、cond
はその節のbody-formsを評価し、 body-formsの最後の値がcond
の値となる。 残りの節は無視する。
conditionの値がnil
であると、 その節は『失敗』し、 cond
はつぎの節へ移りそのconditionを試す。
各conditionがnil
に評価されると、 すべての節が失敗し、cond
はnil
を返す。
節clauseは、つぎの形式でもよい。
(condition) |
この場合、conditionがnil
以外であると、 conditionがcond
フォームの値になる。
以下の例には4つの節があり、 x
の値が、数、文字列、バッファ、シンボルかどうか調べる。
(cond ((numberp x) x) ((stringp x) x) ((bufferp x) (setq temporary-hack x) ; 1つの節に (buffer-name x)) ; 複数個の本体フォーム ((symbolp x) (symbol-value x))) |
最後の節を除くそれよりまえの節がどれも成功しないときには、 最後の節を実行したいことがしばしばある。 これを行うには、(t body-forms)
のように 最後の節のconditionにt
を使う。 フォームt
はt
と評価され、けっしてnil
ではない。 そのため、cond
がこの節に達したときには、 この節が失敗することはない。
たとえば、つぎのとおり。
(cond ((eq a 'hack) 'foo) (t "default")) => "default" |
この式は、a
の値がhack
のときにはfoo
を返し、 さもなければ文字列"default"
を返すcond
である。
任意の条件付き構造は、cond
やif
で表現できます。 したがって、どちらを使うかは好みの問題です。 たとえば、つぎのとおりです。
(if a b c) == (cond (a b) (t c)) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
本節では、if
やcond
とともに用いて複雑な条件を表現するために しばしば使われる3つの構造を説明します。 and
やor
の構造は、 複数の条件付き構造の一種として単独で使うこともできます。
nil
であればt
を返し、 さもなければnil
を返す。 関数not
はnull
と同一であるが、 空リストかどうか調べる場合には、null
を使うことを勧める。and
は、 すべてのconditionsが真であるかどうか調べる。 conditionsを1つ1つ書かれた順に評価して調べる。
conditionsのどれかがnil
に評価されると、 and
の結果は、残りのconditionsに関係なく、nil
になる。 つまり、and
はただちに完了し、 conditionsの残りを無視する。
conditionsすべてがnil
以外であることがわかると、 それらの最後の値がフォームand
の値となる。
例を示そう。 最初の条件は整数1を返し、これはnil
ではない。 同様に、2番目の条件は整数2を返し、nil
ではない。 3番目の条件はnil
なので、残りの条件を評価しない。
(and (print 1) (print 2) nil (print 3)) -| 1 -| 2 => nil |
and
を使ったより現実的な例はつぎのとおり。
(if (and (consp foo) (eq (car foo) 'x)) (message "foo is a list starting with x")) |
(consp foo)
がnil
を返すと(car foo)
は実行されず、 そのためエラーを回避することに注意。
and
は、if
やcond
で表現できる。 たとえば、つぎのとおり。
(and arg1 arg2 arg3) == (if arg1 (if arg2 arg3)) == (cond (arg1 (cond (arg2 arg3)))) |
or
は、 conditionsの少なくとも1つが真であるかどうか調べる。 conditionsを1つ1つ書かれた順に評価して調べる。
conditionsのどれかがnil
以外に評価されると、 or
の結果はnil
以外になる。 そして、or
はただちに完了し、 conditionsの残りを無視する。 戻り値は、nil
以外に評価された値である。
conditionsすべてがnil
であることがわかると、 or
はnil
を返す。
たとえば、つぎの式は、x
が0かnil
であることを調べる。
(or (eq x nil) (eq x 0)) |
and
構造と同様に、or
はcond
で書き表せる。 たとえば、つぎのとおり。
(or arg1 arg2 arg3) == (cond (arg1) (arg2) (arg3)) |
or
をif
で書くこともだいたいできるが、 途中で抜け出せない。
(if arg1 arg1 (if arg2 arg2 arg3)) |
これは完全には同一ではない。 というのは、arg1やarg2を2度評価するからである。 一方、(or arg1 arg2 arg3)
は、 どの引数も一度だけ評価する。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
繰り返しとは、プログラムのある部分を何度も実行することです。 たとえば、リストの各要素や0からnの各整数について 1回ずつある計算を行いたい場合です。 Emacs Lispでこれを行うには、スペシャルフォームwhile
を使います。
while
は、まずconditionを評価する。 結果がnil
以外であれば、formsをテキスト上の順で評価する。 そして、conditionを評価し直し、その結果がnil
以外であれば、 再度formsを評価する。 この処理をconditionがnil
に評価されるまで繰り返す。
繰り返し回数に制限はない。 ループは、conditionがnil
に評価される、 エラーが発生する、throw
によりループから抜け出す (see 節 9.5 非ローカル脱出)のいずれかが起こるまで繰り返される。
フォームwhile
の値はつねにnil
である。
(setq num 0) => 0 (while (< num 4) (princ (format "Iteration %d." num)) (setq num (1+ num))) -| Iteration 0. -| Iteration 1. -| Iteration 2. -| Iteration 3. => nil |
終了検査のまえに各繰り返しごとに実行したいことがあれば、 以下のように、それらと終了検査をprogn
でまとめたものを while
の第1引数にする。
(while (progn (forward-line 1) (not (looking-at "^$")))) |
これは、1行先へ移動し、空行に達するまで、移動を繰り返す。 このwhile
には本体がなく、 終了検査(かつポイントを実際に動かす)だけであるという点で、 風変わりである。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
非ローカル脱出(nonlocal exit)とは、 プログラムのある場所から別の離れた場所へ制御を移すことです。 Emacs Lispでは、エラーの結果として非ローカル脱出が発生します。 非ローカル脱出は、明示的な制御にも使えます。 非ローカル脱出は、脱出対象の構造で作成したすべての変数束縛を解きます。
9.5.1 明示的な非ローカル脱出: catch
とthrow
Nonlocal exits for the program's own purposes. 9.5.2 catch
とthrow
の例Showing how such nonlocal exits can be written. 9.5.3 エラー How errors are signaled and handled. 9.5.4 非ローカル脱出時の後始末 Arranging to run a cleanup form if an error happens.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
catch
とthrow
catch
とthrow
"へのコメント(無し)
ほとんどの制御構造は、その構造内での制御の流れだけに影響します。 関数throw
は、通常のプログラム実行のこのような規則の例外です。 つまり、要求に従って非ローカルな脱出を行います。 (ほかにも例外はあるが、それらはエラー処理のためだけである。) throw
はcatch
の内側で使い、 そのcatch
へ戻ります。
(defun foo-outer () (catch 'foo (foo-inner))) (defun foo-inner () ... (if x (throw 'foo t)) ...) |
フォームthrow
を実行すると、対応するcatch
へ制御が戻り、 そのcatch
はただちに終了します。 throw
に続くコードは実行されません。 throw
の第2引数は、catch
の戻り値として使われます。
関数throw
は、その第1引数に基づいて対応するcatch
を探します。 つまり、catch
の第1引数が throw
に指定されたものにeq
であるcatch
を探します。 そのようなcatch
が複数個ある場合には、 もっとも内側のものを優先します。 したがって、上の例では、throw
はfoo
を指定し、 foo-outer
のcatch
は同じシンボルを指定しているので、 そのcatch
を使います (ただし、これらのあいだには他の一致するcatch
がないとして)。
throw
の実行により、 対応するcatch
までのすべてのLispの構造を抜け出します。 これには関数呼び出しも含みます。 let
や関数呼び出しなどの束縛を作る構造からもこのように抜け出すので、 通常どおり抜け出す場合と同様に束縛を解きます (see 節 10.3 ローカル変数)。 同様に、throw
は、save-excursion
(see 節 29.3 エクスカージョン)で 保存したバッファや位置情報、 save-restriction
で保存したナロイング状態、 save-window-excursion
(see 節 27.16 ウィンドウ構成)で保存した ウィンドウの選択状態も復元します。 さらに、スペシャルフォームunwind-protect
で設定した後始末を このフォームから抜け出すときに実行します(see 節 9.5.4 非ローカル脱出時の後始末)。
throw
は、テキスト上で、 ジャンプ先であるcatch
の内側に現れる必要はありません。 throw
は、catch
内から呼ばれた別の関数からも戻ることもできます。 throw
の実行が、 時間的にcatch
に入ったあとで、かつ、それから抜けるまえである限り、 throw
は対応するcatch
を参照できます。 エディタコマンドループ(see 節 20.11 再帰編集)から抜ける exit-recursive-edit
などのコマンドで throw
を使えるのは、このような理由からです。
Common Lispに関した注意:Common Lispを含むほとんどの他のLispには、 非逐次的に制御を移す方法がいくつかある。 たとえば、
return
、return-from
、go
。 Emacs Lispにはthrow
しかない。
catch
は、関数throw
向けに戻り位置を確立する。 その戻り位置は、tagによって他の戻り位置と区別される。 tagは、nil
以外ならば任意のLispオブジェクトでよい。 引数tagは、戻り位置を確立するまえに、通常どおり評価される。
戻り位置を確立してから、catch
は、bodyのフォームを テキスト上の順に評価する。 エラーや非ローカル脱出なしにフォームの実行が普通に終了した場合、 catch
は、最後の本体フォームの値を返す。
bodyの内側で、tagと同じ値を指定したthrow
が実行されると、 catch
はただちに終了する。 このとき返す値は、throw
の第2引数に指定されたものである。
throw
の目的は、 catch
でまえもって確立しておいた戻り位置へ復帰することである。 引数tagは、さまざまな既存の戻り位置から選ぶために使う。 tagは、catch
で指定した値とeq
である必要がある。 tagに複数の戻り位置が一致する場合には、もっとも内側のものを使う。
引数valueは、対応するcatch
の戻り値として使う。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
catch
とthrow
の例catch
とthrow
の例"へのコメント(無し)
catch
とthrow
の使い方の1つは、 2重のループからの脱出です。 (ほとんどの言語では、これを『go to』で行うであろう。) ここでは、iとjを0から9に変えながら、 (foo i j)
を計算します。
(defun search-foo () (catch 'loop (let ((i 0)) (while (< i 10) (let ((j 0)) (while (< j 10) (if (foo i j) (throw 'loop (list i j))) (setq j (1+ j)))) (setq i (1+ i)))))) |
foo
がある時点でnil
以外を返すと、 ただちに止まってiとjのリストを返します。 foo
がつねにnil
を返すと、 catch
は通常どおりに戻って、その値はnil
です。 というのは、while
の結果はnil
だからです。
2つの巧妙な例をあげましょう。 多少異なる2つの戻り位置が同時に存在します。 まず、同じタグhack
で2つの戻り位置があります。
(defun catch2 (tag) (catch tag (throw 'hack 'yes))) => catch2 (catch 'hack (print (catch2 'hack)) 'no) -| yes => no |
どちらの戻り位置もthrow
に一致するタグなので、 内側のもの、つまり、catch2
で確立したものに戻ります。 したがって、catch2
は値yes
で通常どおり戻り、 この値が表示されます。 最後に、外側のcatch
の2番目の本体フォーム、 つまり、'no
が評価され、外側のcatch
から戻ります。
今度は、catch2
に指定する引数を変更してみます。
(defun catch2 (tag) (catch tag (throw 'hack 'yes))) => catch2 (catch 'hack (print (catch2 'quux)) 'no) => yes |
ここでも2つの戻り位置がありますが、 今度は外側のものだけがタグhack
です。 内側のものはタグquux
です。 したがって、throw
により、外側のcatch
が値yes
を返します。 関数print
はけっして呼ばれず、 本体フォーム'no
もけっして評価されません。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
Emacs Lispが、なんらかの理由で評価できないフォームを評価しようとしたときには、 Emacs Lispはエラー(error)を通知(signals)します。
エラーが通知されると、Emacsのデフォルトの動作は、 エラーメッセージを表示し、現在のコマンドの実行を終了します。 バッファの末尾でC-fを打ったときなどのように、 これはほとんどの場合、適切なことです。
複雑なプログラムでは、単に終了するだけでは満足できないこともあります。 たとえば、プログラムではデータ構造に一時的な変更を加えていたり、 プログラム終了時には削除する必要がある一時的なバッファを作成するでしょう。 そのような場合には、unwind-protect
を使って、 エラー発生時に評価される後始末式(cleanup expressions)を 確立しておきます。 (see 節 9.5.4 非ローカル脱出時の後始末。) 場合によっては、サブルーティンでエラーが発生しても、 プログラムの実行を継続したいこともあるでしょう。 このような場合には、condition-case
を使って、 エラー状態から制御を回復するための エラーハンドラ(error handlers)を確立しておきます。
エラー処理を用いてプログラムのある場所から別の場所へ制御を移す、 という誘惑には耐えてください。 そのかわりにcatch
とthrow
を使いましょう。 See 節 9.5.1 明示的な非ローカル脱出: catch
とthrow
。
9.5.3.1 エラーの通知方法 How to report an error. 9.5.3.2 Emacsのエラー処理方法 What Emacs does when you report an error. 9.5.3.3 エラーハンドラの書き方 How you can trap errors and continue execution. 9.5.3.4 エラーシンボルと条件名 How errors are classified for trapping them.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
ほとんどのエラーは、他の目的で呼び出したLisp関数の内部で『自動的』に 通知されます。 整数のCARを計算しようとしたり、 バッファの末尾で1文字進めようとしたりしたときなどです。 関数error
や関数signal
で、 明示的にエラーを通知することもできます。
ユーザーがC-gを打ったときに発生する中断は、 エラーとは考えませんが、エラーのように扱います。
format
(see 節 4.6 文字と文字列の変換)を適用して作った エラーメッセージを伴ったエラーを通知する。
error
の典型的な使い方を以下に示す。
(error "That is an error -- try something else") error--> That is an error -- try something else (error "You have committed %d errors" 10) error--> You have committed 10 errors |
error
は、2つの引数、 エラーシンボルerror
とformat
が返す文字列を含むリスト でsignal
を呼び出すことで動作する。
警告: 独自のエラーメッセージをそのまま使いたい場合に、 単に
(error string)
とは書かないこと。 stringに`%'が含まれていると、 それは書式付け指定と解釈され、予測不能な結果を招く。 そのかわりに、(error "%s" string)
を使う。
引数error-symbolは、エラーシンボル(error symbol)である 必要がある。 つまり、属性error-conditions
を持つシンボルであり、 その属性値は条件名のリストである。 これにより、Emacsはエラーの異なる種類を分類する。
dataのオブジェクトの個数と重要性はerror-symbolに依存する。 たとえば、エラーwrong-type-arg
では、 リストには2つのオブジェクトがあるはずで、 予期した型を表す述語とその型に一致しなかったオブジェクトである。 エラーシンボルの説明は、see 節 9.5.3.4 エラーシンボルと条件名。
error-symbolとdataの両者は、 任意のエラーハンドラで利用できる。 condition-case
は、ローカル変数に フォーム(error-symbol . data)
のリストを束縛する (see 節 9.5.3.3 エラーハンドラの書き方)。 エラーが処理されないと、これらの2つの値はエラーメッセージの表示に使われる。
関数signal
はけっして戻らない (しかし、Emacsの古い版では戻る場合もある)。
(signal 'wrong-number-of-arguments '(x y)) error--> Wrong number of arguments: x, y (signal 'no-such-error '("My unknown error condition")) error--> peculiar error: "My unknown error condition" |
Common Lispに関した注意:
Emacsには、Common lispの継続可能なエラーの概念に相当するものはない。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
エラーが通知されると、signal
は、 エラーに対する有効なハンドラ(handler)を探します。 ハンドラは、Lispプログラムの一部でエラーが発生した場合に 実行されるように指定されたLisp式の列です。 エラーに対して適用可能なハンドラがあると、 そのハンドラが実行され、ハンドラに続いて制御は復旧します。 ハンドラは、そのハンドラを設定したcondition-case
の環境で実行されます。 condition-case
の内側で呼び出された関数はすべて終了しているので、 ハンドラからそれらへ戻ることはできません。
エラーに適用可能なハンドラがなければ、 現在のコマンドは終了し、制御はエディタコマンドループへ戻ります。 というのは、コマンドループには、 すべての種類のエラーに対する暗黙のハンドラがあるからです。 コマンドループのハンドラは、エラーシンボルと関連するデータを使って エラーメッセージを表示します。
明示的なハンドラがないエラーは、Lispデバッガを呼び出すこともあります。 変数debug-on-error
(see 節 17.1.1 エラーによるデバッガの起動)が nil
以外であると、デバッガが有効になります。 エラーハンドラと違って、デバッガはエラーの環境で実行されるので、 エラー時の変数の正確な値を調べることができます。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
エラーを通知することの普通の効果は、 実行中のコマンドを終了し、Emacsのエディタコマンドループにただちに戻ります。 読者のプログラムの一部で発生したエラーを捕捉するようにするには、 スペシャルフォームcondition-case
を使ってエラーハンドラを設定します。 単純な例はつぎのようになります。
(condition-case nil (delete-file filename) (error nil)) |
これはfilenameという名前のファイルを削除しますが、 エラーが発生するとどんなエラーでも捕捉してnil
を返します。
condition-case
の第2引数を 保護されたフォーム(protected form)と呼びます。 (上の例では、保護されたフォームはdelete-file
の呼び出し。) このフォームの実行を開始するとエラーハンドラが有効になり、 このフォームから戻るとエラーハンドラは取り除かれます。 そのあいだは、つねにエラーハンドラは有効です。 特に、このフォームから呼び出される関数の実行中、 それらのサブルーティンの実行中などには、エラーハンドラは有効です。 これは大切なことで、厳密にいえば、 エラーが通知されるのは、保護されたフォームから呼び出された (signal
やerror
を含む)Lisp基本関数の実行中であって、 保護されたフォームそのものからではないからです。
保護されたフォームのうしろにある引数は、ハンドラです。 各ハンドラは1つ以上の(シンボルである)条件名 (condition names)を列挙し、処理するエラーを指定します。 エラーが通知されたときのエラーシンボルも条件名のリストを定義します。 それらに共通の条件名があるとき、 エラーハンドラがエラーに適用されます。 上の例では、1つのハンドラがあり、条件名は1つ、error
を指定しています。 この条件名はすべてのエラーを意味します。
適用可能なハンドラの探索では、 もっとも最近に確立されたハンドラから始めて、 確立されたすべてのハンドラを調べます。 したがって、フォームcondition-case
が2つ入れ子になっていて 同じ名前のハンドラを確立していると、内側のものが実際に処理を受け持ちます。
フォームcondition-case
でエラーが処理されるときには、 debug-on-error
でエラーによりデバッガを起動するように指定してあっても デバッガは実行されません。 See 節 17.1.1 エラーによるデバッガの起動。 condition-case
で捕捉されるエラーをデバッグしたいときには、 変数debug-on-signal
にnil
以外の値を設定します。
エラーを処理できる場合には、制御はハンドラに移ります。 こうするまえに、Emacsは、抜け出し対象となる束縛作成構造が設定した すべての変数束縛を解き、抜け出し対象となるフォームunwind-protect
すべての後始末を実行します。 ハンドラに制御が移ると、ハンドラの本体を実行します。
ハンドラ本体の実行を完了すると、 フォームcondition-case
から戻ります。 ハンドラを実行するまえに保護されたフォームから完全に抜けているので、 ハンドラでは、エラー発生時点から再開したり、 保護されたフォームの内側で作られた変数束縛を調べたりすることはできません。 ハンドラでできることは、後始末をして先へ進むことだけです。
condition-case
構造は、insert-file-contents
の呼び出しで ファイルのオープンに失敗するなどの予測可能なエラーを捕捉するために しばしば使われます。 プログラムがユーザーから読み取った式を評価する場合のように、 まったく予測不可能なエラーを捕捉するためにも使われます。
エラー通知とエラー処理は、throw
とcatch
に多少似ていますが、 それらはまったく別の機能です。 catch
ではエラーを捕捉できませんし、 エラーハンドラではthrow
を処理できません (しかしながら、適切なcatch
がないthrow
を使うと、 処理できるエラーを通知する)。
condition-case
の値になる。 この場合、condition-case
はなんの効果もない。 フォームcondition-case
で違いがでるのは、 protected-formの実行中にエラーが起こった場合である。
各handlersは、(conditions body...)
の形式の リストである。 ここでconditionsは、処理すべきエラーの条件名か条件名のリストである。 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)がある。 そのシンボルの属性error-conditions
は、 条件名のリストである(see 節 9.5.3.4 エラーシンボルと条件名)。 Emacsは、有効なフォームcondition-case
すべてを探索し、 これらの条件名を1つ以上指定したハンドラを探す。 もっとも内側の一致するcondition-case
がエラーを処理する。 このcondition-case
の内側では、 適用可能な最初のハンドラがエラーを処理する。
ハンドラの本体の実行を完了すると、 condition-case
は通常のように戻り、 ハンドラの本体の最後のフォームの値を全体としての値に使う。
引数varは変数である。 condition-case
は、protected-formを実行するときには この変数を束縛せず、エラーを処理するときだけ束縛する。 そのとき、varはローカルにエラー記述 (error description)に束縛される。 これは、エラーの詳細を与えるリストである。 エラー記述は、(error-symbol . data)
の形式である。 ハンドラは、動作を決定するためにこのリストを参照できる。 たとえば、ファイルのオープンに失敗したエラーであれば、 dataの第2要素、エラー記述の第3要素がファイル名である。
varがnil
であると、変数を束縛しなことを意味する。 そうすると、ハンドラではエラーシンボルと関連するデータを使えない。
ゼロ除算の結果であるエラーを処理するcondition-case
の使用例を示します。 ハンドラはエラーメッセージを(ベルを鳴らさずに)表示して、 大きな数を返します。
(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
を指定しているので、 ゼロ除算エラーだけを処理します。 少なくともこの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
;; これは関数
|
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
エラーを通知するときには、読者が意図するエラーの種類を指定する エラーシンボル(error symbol)を指定します。 各エラーには、それを分類する一意な名前があります。 これは、Emacs Lisp言語で定義されたエラーを細分類したものです。
これらの細分類は、エラー条件(error conditions)と呼ばれる より大きなクラスの階層にまとめられています。 エラー条件は、条件名(condition names)で識別します。 もっとも細かい分類は、エラーシンボルそのものです。 各エラーシンボルは条件名でもあります。 より大きなクラスを表す条件名error
もあります。 これはすべての種類のエラーを表します。 したがって、各エラーには、1つ以上の条件名があります。 つまり、error
、error
とは別のエラーシンボル、あるいは、 その中間の分類に属するものです。
あるシンボルがエラーシンボルであるためには、そのシンボルには、 条件名のリストを与える属性error-conditions
があることが必要です。 このリストは、そのエラーが属するエラー条件を定義します。 (エラーシンボルそのものと、シンボルerror
は、 つねにこのリストの要素であること。) したがって、条件名の階層は、 エラーシンボルの属性error-conditions
で定義されます。
error-conditions
リストに加えて、 エラーシンボルには、属性error-message
も必要です。 この属性の値は、そのエラーが処理されないときに表示される文字列です。 属性error-message
があるのに、それが文字列でなければ、 エラーメッセージ`peculiar error'を使います。
以下に、新たなエラーシンボルnew-error
の定義方法を示します。
(put 'new-error 'error-conditions '(error my-own-errors new-error)) => (error my-own-errors new-error) (put 'new-error 'error-message "A new error") => "A new error" |
このエラーには、3つの条件名があります。 もっとも細かい分類であるnew-error
、 それより大きな分類とであると考えているmy-own-error
、 もっとも大きな分類であるerror
です。
エラー文字列は大文字で始めるべきですが、ピリオドで終えません。 これは、Emacsの他の慣習と整合をとるためです。 普通、Emacs自身がnew-error
を通知することはありえません。 つぎのように、読者のコードで明示的に signal
(see 節 9.5.3.1 エラーの通知方法)を呼んだときだけです。
(signal 'new-error '(x y)) error--> A new error: x, y |
このエラーは、3つの条件名のどれでも処理できます。 つぎの例は、new-error
と クラスmy-own-errors
の任意の他のエラーを処理します。
(condition-case foo (bar nil t) (my-own-errors nil)) |
エラーを分類する重要な方法は、それらの条件名によることです。 つまり、エラーに一致するハンドラを探すために条件名を使います。 エラーシンボルは、意図したエラーメッセージと条件名のリストを指定する 簡便な方法を提供するだけです。 signal
に、1つのエラーシンボルではなく、 条件名のリストを指定するのではわずらわしいでしょう。
一方、条件名なしにエラーシンボルだけを使うのでは、 condition-case
の能力をいちじるしく損ないます。 条件名があることで、エラーハンドラを書くときにさまざまなレベルに 一般化してエラーを分類できるのです。 エラーシンボルだけを使ったのでは、 最細分類以外のレベルを削除してしまうことになります。
すべての標準エラー名とそれらの条件名については、 See 節 D. 標準のエラー。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
unwind-protect
構造は、データ構造を一時的に整合性のない状態に するときには本質的です。 この構造により、エラーや非ローカル脱出が起こったときに、 データの整合性を回復できます。
unwind-protect
は、bodyからどのように制御が離れた場合にも cleanup-formsの実行を保証して、bodyを実行する。 bodyは通常どおり完了するか、 throw
を実行してunwind-protect
から脱出するか、 エラーを引き起こす。 いずれの場合でも、cleanup-formsは評価される。
フォームbodyが正常に終了すると、 unwind-protect
は、cleanup-formsを評価したあとに、 フォームbodyの最後の値を返す。 フォームbodyが完了しなかった場合、 unwind-protect
は普通の意味での値は返さない。
unwind-protect
が保護するのはbodyだけである。 cleanup-formsそのもののどれかが(throw
やエラーで) 非ローカル脱出を行うと、unwind-protect
は、 cleanup-formsの残りを評価することを保証しない。 cleanup-formsのどれかが失敗するとトラブルになる危険性がある場合には、 cleanup-formsを別のunwind-protect
で保護する。
フォームunwind-protect
の現在の入れ子の個数は、 ローカル変数束縛の個数とともに数えられ、 max-specpdl-size
に制限されている(see 節 10.3 ローカル変数)。
たとえば、表示しないバッファを一時的に作成し、 終了前に確実にそれを消去したいとしましょう。
(save-excursion (let ((buffer (get-buffer-create " *temp*"))) (set-buffer buffer) (unwind-protect body (kill-buffer buffer)))) |
変数buffer
を使わずに(kill-buffer (current-buffer))
と 書くだけで十分だと考えるかもしれません。 しかし、別のバッファに切り替えたあとでbodyでエラーが発生した場合には、 上の方法はより安全です。 (あるいは、bodyの周りに別のsave-excursion
を書いて、 一時バッファを消去するときに、それがカレントバッファになることを 保証する。)
Emacsには、上のようなコードに展開されるwith-temp-buffer
という 標準マクロがあります(see 節 26.2 カレントバッファ (2003/10/30))。 本書で定義しているマクロのいくつかでは、 このようにunwind-protect
を使っています。
ファイル`ftp.el'から持ってきた実際の例を示しましょう。 リモートの計算機への接続を確立するプロセス(see 節 36. プロセス)を作ります。 関数ftp-login
は、その関数の作成者が予想できないほどの 数多くの問題に対してとても敏感ですから、 失敗したときにプロセスを消去することを保証するフォームで保護します。 さもないと、Emacsは、無用なサブプロセスで満たされてしまいます。
(let ((win nil)) (unwind-protect (progn (setq process (ftp-setup-buffer host file)) (if (setq win (ftp-login process host user password)) (message "Logged in") (error "Ftp login failed"))) (or win (and process (delete-process process))))) |
この例には、小さなバグが1つあります。 ユーザーがC-gを打って中断しようとして、かつ、 関数ftp-setup-buffer
の終了後に 変数process
を設定するまえに実際に中断が行われると、 プロセスは消去されません。 このバグを直す簡単な方法はありませんが、 少なくとも、ほとんど起こりえません。
[ << ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |