検索エンジンから来た人に注意このマニュアルは、Emacs ver. 19.x 向けのマニュアルを Mule 2.x の開発にあたり邦訳したものを、 電脳外道学会がミラーリングしているものであり、旧版製品パラノイアであるところの椅子人の趣味によるものです。しかしながら、現在の Emacs の主流は ver. 20 以降であり、ver 19.x と ver 20.x とでは、仕様の違いが少なからずあります。 したがって、一般的な Emacs ユーザーにとっては、
このマニュアルと実機の動作とが符合しない場合があります。
|
Lispプログラムは式または形式(form)(see section 形式の種類)から成っています。 私たちは、それらを制御構造(control structure)で囲むことによって、 形式の実行の順番を制御することができます。制御構造とは、 囲んでいる形式を、いつ実行するか、実行するかしないか、何回 実行するかを制御する特殊形式のことです。
最も単純な実行順序は順次実行です。最初に形式a、 次に形式bというように実行します。これは関数の本体、 またはLispコードのファイルの最上位レベルに、複数の形式を連続して 記述したときの実行であって、それらの形式は記述された順番に実行されます。 私たちはこれをテキストどおりの順序(textual order)と呼んで います。例えば、もし関数の本体が二つの形式aとbから 成っているなら、関数は最初にa、次にbを評価し、 bの値が関数の値になります。
明示的な制御構造は順次以外の実行順序を可能にします。
Emacs Lispは、順次実行の変種、条件分岐、繰返し、および(制御された) ジャンプを含んだ数種類の制御構造を提供していて、そのすべてを以下に説明 します。 組込みの制御構造は、その部分形式が必ずしも評価されるわけではなく、 また、順番に評価されるわけでもないので、特殊形式になっているのです。 マクロを使って、独自の制御構造を定義することができます (see section マクロ)。
形式を出現順に評価することは、ある形式から他の形式へ制御を移す方法の
なかで、もっともよく使われるものです。
関数本体のような、いくつかの文脈においては自動的にこのようになります。
その他の場所で、これを行うには制御構造の構文要素を使用しなければ
なりません。progn
はLispの最も簡単な制御の構文要素です。
特殊形式progn
は以下のようなものです。
(progn a b c ...)
これは形式a, b, cなどを、その順番で実行することを
意味しています。これらの形式はprogn
形式の本体と呼ばれます。
本体の最後の形式の値がprogn
全体の値になります。
初期のLispでは、progn
は、連続した二つ以上の形式を実行し、
その最後の値を使用するための唯一の方法でした。
しかし、プログラマは、(その時点では)ただ一つの形式だけが許されていた
関数の本体にも、
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"
以下の二つの制御構造も同様に形式の並びを評価しますが、異なった値を 返します。
(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"
次の例では変数 x
に設定されたリストから先頭の要素を削除し、
その削除した要素の値を返しています。
(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"
条件分岐の制御構造は選択肢の中から選び出すことをします。
Emacs Lispは、二つの条件分岐の形式を持っています。
他の言語とほとんど同じであるif
と、一般化されたcase文で
あるcond
です。
if
はconditionの値を元にthen-formかelse-formsかを
選択します。
評価されたconditionがnil
でないならば、then-form
が評価され、その結果が返されます。
そうでなければ、else-formsがテキストどおりの順序で評価され、
最後のものの値が返されます。
(if
のelse部は暗黙のprogn
の例です。
see section 順次実行)
conditionが値nil
を持ち、else-formsが与えられて
いなければ、if
はnil
を返します。
if
は選択されない枝はまったく評価されない
(無視される)ので、特殊形式になっています。
したがって、以下の例でprint
は決して呼ばれないのでtrue
は印字されません。
(if nil (print 'true) 'very-false) => very-false
cond
は任意個の選択肢から選択します。
cond
の個々のclauseはリストでなければなりません。
このリストのCARはconditionです。
残りの要素は(もしあれば)body-formsです。
したがって句(clause)は次のようになります。
(condition body-forms...)
cond
は個々の句のconditionを評価していくことによって、
その句の並びをテキストどおりの順序で試します。
conditionの値がnil
でなければ、その句は「成功」します。
その場合、cond
はそのbody-formsを評価し、
body-formsの最後の値がcond
の値となります。
残りの句は無視されます。
conditionの値がnil
ならば、その句は「失敗」し、
cond
は次の句に制御を移し、その conditionを試していきます。
すべてのconditionがnil
に評価された場合、すなわち、
すべての句が失敗した場合、cond
はnil
を返します。
句は次のようになっているかもしれません。
(condition)
その場合、conditionのテスト結果がnil
でなければ、
conditionの値がcond
形式の値になります。
以下の例では四つの句を持っていて、それぞれx
の値が
数値、文字列、バッファ、シンボルのどれかであるかをテスト
します。
(cond ((numberp x) x) ((stringp x) x) ((bufferp x) (setq temporary-hack x) ; 一つの句に (buffer-name x)) ; 複数の body-forms ((symbolp x) (symbol-value x)))
最後の句より前の句がどれも成功しなかった場合には、
その最後の句を実行したいということがよくあります。
これを行うために私たちは次のように最後の句のconditionと
してt
を使用します。(t body-forms)
。
形式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))
この節では複雑な条件を表現するためにif
やcond
とともによく使用される三つの構文要素を説明します。
構文要素and
とor
は、ある種の複合した条件を
組み合わせるものとして別個に使用されることもあります。
nil
であればt
を返し、
そうでなければnil
を返します。
関数not
はnull
と同一ですが、
空リストのテストにはnull
の方を使用することを私たちは勧めます。
and
は、すべてのconditionsが真であるかを
テストします。それはconditionsを書かれた順序で一つずつ
評価していくことによって行なわれます。
conditionsのうちのどれかがnil
に評価されれば、
残りのconditionsがどうであろうともand
の
結果は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 は 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の一つが真で
あるかをテストします。それは conditionsを書かれた順序で一つずつ
評価していくことによって行なわれます。
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))
if
を使ってもor
とほとんど同じものを書き表すことが
できますが、完全ではありません。
(if arg1 arg1 (if arg2 arg2 arg3))
これはarg1またはarg2を2回評価するので、
完全には等しくありません。
一方、(or arg1 arg2 arg3)
では、
何度も評価されることは決してありません。
繰り返しはプログラムの一部を反復して実行することを意味します。
例えば、あなたはリストの個々の要素について一度ずつ、
または0からnまでの個々の整数について一度ずつ、
ある計算を行ないたいとします。
あなたは特殊形式while
を使用してEmacs Lispでこれを
行なうことができます。
while
は最初にconditionを評価します。
その結果がnil
でなければ、テキストどおりの順序でformsを
評価します。そして、conditionを再評価し、その結果が nil
でなければformsを再び評価します。
この処理はconditionがnil
に評価されるまで繰り返します。
繰り返しの回数には制限がありません。その繰り返しは、conditionがnil
に評価されるか、エラーまたはthrow
による脱出(see section 非局所脱出)
が発生するまで継続します。
形式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
個々の繰り返しで終了テストの直前に毎回何かを実行したいならば、
以下に示すようにwhile
の最初の引数としてprogn
を置き、
その中に、終了テストと一緒に置きます。
(while (progn (forward-line 1) (not (looking-at "^$"))))
これは前方に1ライン移動し、空行に達するまで行移動を継続します。
while
が本体を持たず、終了テストだけ(ポイントの移動という
実際の処理も行うが)という点で、まれなものです。
非局所脱出(nonlocal exit)はプログラム中のある位置から、 別の離れた位置への制御の移行です。非局所脱出はEmacs Lispでは、エラーの 結果として生じることがあります。また、明示的な制御のもとで、それを使用する こともできます。非局所脱出は脱出しようとしている構文要素にある全ての 変数の束縛を解きます。
catch
と throw
たいていの制御構造は構造自身内での制御の流れにのみ影響します。
関数throw
は、この通常のプログラム実行の規則の例外です。
それは要求により非局所脱出を実行します
(他の例外もありますが、それはエラーをハンドルするだけのためのものです)。
throw
はcatch
の内側で使用され、そのcatch
に
飛びます。例えば:
(catch 'foo (progn ... (throw 'foo t) ...))
throw
は該当するcatch
の直後に制御を移します
(該当するcatch
から直ちにリターンする)。
throw
以降のコードは実行されません。
throw
の2番目の引数はcatch
の戻り値として使用されます。
throw
とcatch
は最初の引数によって対応がつけられます。
throw
は、最初の引数がthrow
で指定されたもの
とeq
であるcatch
を検索します。
したがって上記の例では、throw
は、foo
を指しており、
catch
も同じシンボルを指定しているので、そのcatch
が
適用されます。二つ以上の適用可能なcatch
がある場合は、
最も内側のものが優先されます。
throw
を実行すると、対応するcatch
までの、
すべてのLisp構文要素(関数呼出しも含む)から脱出します。
let
または関数呼出しのような束縛をつくる構文要素から
このようにして脱出した場合、
それらの構文要素が普通に終了するのと同様に、
その束縛は解除されます(see section ローカル変数)。
同様に、throw
はsave-excursion
(see section 脱線)に
よって保存されていたバッファとポジション、save-restriction
によって保存されていたナローイング状態、save-window-excursion
によって保存されていたウィンドウ・セレクション(see section ウィンドウ構成)
を復元します。その形式を脱出するときには、unwind-protect
特殊形式
によって設定されたあらゆるクリーンアップも行ないます(see section 非局所脱出のクリーンアップ)。
throw
は、それがジャンプする先のcatch
がレキシカルに存在して
いることを必要としません。それはcatch
の中で呼び出される別の
関数からも同様に呼び出されることができます。throw
が評価順で
catch
の後であり、それから抜けるのが評価順で前であるかぎり、
その catch
へのアクセスを持ちます。これが
エディタのコマンド・ループに戻る exit-recursive-edit
(see section 再帰編集)のようなコマンドでthrow
が使用できる
理由です。
Common Lisp注意書き: Common Lispを含む、ほとんどの他のバージョン のLispは非順次的に制御を移す方法をいくつか持っています。例えば、
return
,return-from
,go
があります。Emacs Lisp はthrow
だけを持ちます。
catch
はthrow
関数のための復帰位置を確立します。
復帰位置はtag(任意の Lisp オブジェクトを指定可能)によって
他の復帰位置と区別されます。普通は復帰位置が確立される前に、
引数tag
が評価されます。
有効な復帰位置を持って、catch
は、テキストどおりの順序でbody
の形式を評価します。エラーまたは非局所脱出なしに、普通にその形式が
実行されるならば、bodyの最後の形式の値がcatch
から
返されます。
throw
が同じ値tagを指定してbodyの中で行なわれる
ならば、直ちにcatch
を抜けます。それが返す値は
throw
の2番目の引数として指定されたものです。
throw
の目的は前もってcatch
によって設定された復帰
位置に戻ることです。引数tagは、いくつかの存在する復帰位置
の中から選択するために使用されます。それはcatch
に指定された
値とeq
にならなければなりません。複数の復帰位置がtag
と一致する場合、最も内側のものが使用されます。
引数valueは、そのcatch
から返す値として使用
されます。
実効的にタグtagに一致する復帰位置がない場合には、
no-catch
エラーがデータ(tag value)
をともなって発行されます。
catch
とthrow
の例
catch
とthrow
を使用する一つの方法は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
の結果であるので)。
次に、二つのトリッキーな例を示します。 それらは、同時に二つの復帰位置を示していますが、わずかに異なっています。
最初のものには同じタグhack
をもつ二つの復帰位置があります。
(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
この例も二つの復帰位置を持ちますが、外側のものだけがタグhack
を持ちます。内側のものは、その代わりにタグquux
を持っています。
したがって、throw
により、外側のcatch
が値yes
を返
します。関数print
は決して呼び出されず、本体の
形式'no
は決して評価されません。
Emacs Lispは何かの理由によって評価できない形式を評価しようとした場合、 エラー(error)を発行(signal)します。
エラーが発行された場合、Emacsのデフォルトの反応はエラーメッセージを 印字し、現在のコマンドの実行を終了することです。 これは、ほとんどの場合適切なものです。 例えば、バッファの最後でC-fをタイプするような場合です。
複雑なプログラムでは、単純な終了が望むものでは
ないかもしれません。例えば、そのプログラムはデータ構造の一時的な変更を行って
いるかもしれないし、プログラムが終了する前に消去されるべき、
一時的なバッファを作成しているかもしれません。そのような場合には
エラーの場合に評価されるクリーンアップ式(cleanup expression)を設定する
unwind-protect
を使用します(see section 非局所脱出のクリーンアップ)。ときには
サブルーチンでエラーがあっても、実行の継続を
望むかもしれません。このような場合には、エラーの場合に制御を回復するための
エラー・ハンドラ(error handler)を設定する condition-case
を
使用します。
プログラムのある場所から別の場所に制御を移すためにエラーのハンドルを
使用する、という誘惑には抵抗してください。その代わりにはcatch
とthrow
を使用してください。See section 明示的な非局所脱出: catch
と throw
。
ほとんどのエラーは、他の目的で呼び出しているLisp プリミティブ
(例えば、整数のCARを取ろうとしたり、バッファの最後で前方に
移動しようとすること)によって「自動的に」発行されます。
一方、関数error
とsignal
によって、明示的にエラーを発行する
こともできます。
とりやめ(ユーザが C-gをタイプしたときに起きること)は エラーとは考えられませんが、ほとんどエラーと同様にハンドル されます。See section とりやめ。
format
(see section 文字と文字列間の変換)を
format-stringとargsに適用し
エラー・メッセージを作り、エラーを発行します。
以下はerror
の典型的な使用例を示しています。
(error "You have committed an error. Try something else.") error--> You have committed an error. Try something else. (error "You have committed %d errors." 10) error--> You have committed 10 errors.
error
は、二つの引数(エラー・シンボルerror
とformat
によって返される文字列を含んだリスト)とともにsignal
を呼び出す
ことをします。
文字列をエラー・メッセージとしてそのまま使用したい場合、
(error string)
とだけ書いてはいけません。stringが
`%' を含んでいる場合、それは整形指定として解釈され、不正な結果
になってしまいます。その代わりに、(error "%s" string)
としてください。
引数error-symbolは、エラー・シンボル(error symbol)
(値が条件名のリストである属性error-conditions
をもつシンボル)で
なければなりません。これが、Emacs Lispが種々のエラーを分類する
やり方です。
data中のオブジェクトの数と意味はerror-symbolに依存
します。例えば、wrong-type-arg
エラーでは、リストに二つの
オブジェクトを入れます。期待されている型を記述する述語と、
その型との適合に失敗したオブジェクトです。エラー・シンボルの説明は、
See section エラー・シンボルと条件名。
error-symbolとdataの両方を、エラーをハンドルする任意の
エラー・ハンドラは利用できます。condition-case
はローカル変数に
形式(error-symbol . data)
のリストを束縛します
(see section エラーをハンドルするコードの記述)。エラーがハンドルされなければ、これら二つの
値がエラー・メッセージの印字に使用されます。
関数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 LispにはCommon Lispのcontinuableエラー の概念に相当するものはありません。
エラーが発行された場合、signal
は、そのエラーについて有効な
ハンドラ(handler)を探します。ハンドラは、Lispプログラムの一部で
エラーが起きた場合に実行されるように指定されたLisp式のならびです。
そのエラーが適切なハンドラを持っていれば、そのハンドラが実行され、
そのハンドラの後から、制御が再開されます。そのハンドラは、それを、
設定されたcondition-case
の環境で実行されます。
condition-case
で呼ばれるすべての関数は、すでに脱出しているので、
ハンドラはそれらに戻れません。
そのエラーに対して適切なハンドラがなければ、現在のコマンドは終了させられ、 制御はエディタのコマンド・ループに戻ります。コマンド・ループには、 あらゆる種類のエラーを扱う暗黙のハンドラがあるからです。 コマンド・ループのハンドラはそのエラー・シンボルと、エラー・メッセージ の印字に関連したデータを使用します。
明示的なハンドラを持たないエラーはLispデバッガを呼び出すことが
できます。デバッガは変数debug-on-error
(see section エラーでデバッガにはいる)
がnil
でない場合に有効にされます。エラー・ハンドラとは違い、
デバッガはエラーの環境で動作するので、あなたはエラーの時点
での変数の値を正確に調べることができます。
エラーを発行することの通常の効果は動作しているコマンドを終了し、
直ちにEmacsエディタのコマンド・ループに戻ることです。
特殊形式condition-case
でエラー・ハンドラを設定すると、
プログラムのある部分で起こるエラーを捕捉するようにすることが
できます。以下に簡単な例を示します。
(condition-case nil (delete-file filename) (error nil))
これは、filenameという名前のファイルを消去し、
どんなエラーでもそれが起きたときには捕捉し、nil
を返します。
condition-case
の2番目の引数は保護形式(protected form)です
(先の例での保護形式はdelete-file
を呼び出すことです)。
この形式の実行が始まるときにエラー・ハンドラが有効になり、
この形式が戻るときに無効化されます。それは介在する時間のすべてにおいて
有効状態にあります。特に、この形式、そのサブルーチン、その他
によって呼び出される関数の実行中には有効状態にあります。
厳密な話、エラーは保護形式によって呼び出されるLispプリミティブ
(signal
とerror
を含む)によって発行されるのであって、
保護形式自身によって発行されるものではないので、これはよいことです。
引数の中で、保護形式の後にはハンドラが続きます。個々のハンドラには、
ハンドルするエラーを指定する、一つ以上の(シンボルである)
状態名(condition name)のリストを置きます。エラーを発行するときに
指定するエラーシンボルも、状態名のリストを定義します。それらが共通の
状態名をもつならば、そのハンドラがエラーに適合します。先の例では
一つのハンドラがあり、それはすべてのエラーをカバーする一つの
状態名error
を指定しています。
適用可能なハンドラの探索は最も最近に設定されたハンドラから始め、すべての
設定されたハンドラをチェックします。したがって、二重にネストしている
condition-case
形式が同じエラーをハンドルするようになっている
場合には、二つのうちの内側のものが実際にそれをハンドルすることになります。
エラーがハンドルされるときには、制御がハンドラに渡ります。これが起こる前に、
Emacsは抜けようとしている束縛構文要素によって束縛されている全ての変数の束縛を
解除し、抜けたすべてのunwind-protect
形式のクリーンアップを実行
します。いったん、制御がハンドラに到達すると、そのハンドラの本体が実行
されます。
ハンドラの本体の実行の後、実行はcondition-case
形式から
戻ることで続行します。保護形式はハンドラの実行前に完全に抜けて
いるので、ハンドラはエラー時点の実行を再開することはできず、
保護形式で束縛していた変数を調べることもできません。行えることの
すべてはクリーンアップと続行です。
condition-case
は、insert-file-contents
の呼び出しで
ファイルのオープンに失敗するといった、予想できるエラーを捕捉するのに
よく使用されます。また、プログラムがユーザから読み込んだ式を評価する
ときに起きるような、まったく予測不可能なエラーを捕捉するのにも
使用されます。
エラーの発行とハンドルは、throw
とcatch
と類似している点が
ありますが、まったく異なった機能です。エラーはcatch
で
捕まえることはできませんし、throw
はエラー・ハンドラでハンドルする
ことができません(しかし、ふさわしいcatch
がないthrow
を
使うと、ハンドルできるエラーを発行します)。
condition-case
形式の
値になります。この場合、condition-case
はまったく効果を持ちません。
protected-formの実行中にエラーが発生した場合、
condition-case
は違う動作を行ないます。
handlersのそれぞれは、形式(conditions body...)
のリストです。conditionsはハンドルされるエラーの状態名、
または状態名のリストです。bodyは、このハンドラがエラーをハンドル
するときに実行される一つ以上の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 section エラー・シンボルと条件名)。
Emacsは、これらの状態名のうちの一つ以上を指定するハンドラをすべての
有効なcondition-case
形式から検索します。最も内側で一致する
condition-case
がエラーをハンドルします。このcondition-case
に
おいて、最初の適用可能なハンドラがエラーをハンドルします。
ハンドラの本体の実行の後、condition-case
からは普通に戻り、
ハンドラの本体の中で最後の形式の値を、その全体の値として使用します。
引数varは変数です。condition-case
はprotected-form
を実行しているとき、この変数を束縛せず、エラーをハンドルするときにのみ
束縛します。その時点では変数varを形式
(error-symbol . data)
に局所的に束縛し、エラーの詳細
を与えます。ハンドラは何を行うかを決定するために、このリストを
参照することができます。例えば、エラーが、ファイルのオープンに失敗
したというものであれば、ファイル名はdataの2番目の要素
(var の3番目の要素)になっています。
varがnil
ならば、変数を束縛する手段はありません。
ハンドラは、エラー・シンボルおよび関連するデータを使えません。
以下は0による除算から起きるエラーをハンドルするためにcondition-case
を使用した例です。ハンドラは警告メッセージを印字し、非常に大きな数を
返します。
(defun safe-divide (dividend divisor) (condition-case err ;; 保護形式。 (/ dividend divisor) ;; ハンドラ。 (arith-error ; 状態。 (princ (format "Arithmetic error: %s" err)) 1000000))) => safe-divide (safe-divide 5 0) -| Arithmetic error: (arith-error) => 1000000
ハンドラは状態名arith-error
を指定しているので、0による除算だけ
をハンドルします。他の種類のエラーはハンドルされません。少なくとも、
このcondition-case
によってはハンドルされません。したがって、
(safe-divide nil 3) error--> Wrong type argument: integer-or-marker-p, nil
以下は、あらゆる種類のエラー(error
で発行されたものも含む)を
捕らえる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
エラーを発行するときには、あなたが念頭に置いているエラーの種類を 示すエラー・シンボル(error symbol)を指定します。個々のエラーは、 それを分類するために、唯一無二のエラー・シンボルを持っています。これは Emacs Lisp言語で定義された、最も優れたエラーの分類法です。
この狭い分類法は
状態名(condition name)で識別されるエラー状態(error condition)
と呼ばれる、より幅のある種別の階層にグループ化されています。そのような
種別の中で最も狭いものが、エラー・シンボルそのものです。個々の
エラー・シンボルは状態名でもあります。より広い種別の状態名もあり、
それはあらゆる種類のエラーをとる状態名error
にまで至ります。
したがって、個々のエラーは一つ以上の状態名を持っています。
すなわち、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"
このエラーは三つの状態名を持っています。
最も狭い分類であるnew-error
、
それよりは広い分類であろうmy-own-errors
、そして
すべてに渡る最も広いものであるerror
です。
エラーの文字列は英大文字で始まるべきですが、ピリオドで終るべきでは
ありません。これはEmacsの他のものとの一貫性のためです。
もちろんEmacsは、それ自身ではnew-error
を発行しません。
自分のコードでsignal
(see section エラーを発行する方法)を
明示的に呼び出すことによってのみ、これを行うことができます。
(signal 'new-error '(x y)) error--> A new error: x, y
このエラーは三つの状態名のうちのどれかでハンドルすることができます。
以下の例は、new-error
とmy-own-errors
に分類される
任意のエラーをハンドルします。
(condition-case foo (bar nil t) (my-own-errors nil))
エラーを分類する有意義な方法は、状態名(エラーをハンドラと一致させる
ために使用する名前)によることです。エラーシンボルは意図した
エラー・メッセージと状態名のリストとを指定するのに
便利な方法としてのみ役立ちます。一つのエラー・シンボルではなく、
状態名のリストをsignal
に与えるのは面倒なことです。
逆に、状態名ではなく、エラー・シンボルを使用することは
condition-case
の能力を明らかに減少させます。
状態名は、あなたがエラー・ハンドラを書くときに、普遍性のあるさまざまなレベルに
エラーを分類することを可能にします。エラー・シンボル単独で使用することは
最も狭いレベルの分類以外をすべてふるい落します。
すべての標準のエラー・シンボルとその状態の一覧は、See section 標準のエラー。
構文要素unwind-protect
は、一時的に、あるデータ構造を一貫性のない
状態に置くときには常に必須となります。つまり、エラーまたはthrow
の場合に、データが一貫していることを保証することができます。
unwind-protect
は、
制御がbodyから離れた場合、そこでどのようなことが起ころうとも
cleanup-formsが評価される、という保証の上でbodyを実行
します。bodyが普通に完了する場合、
unwind-protect
の外にthrow
が実行される場合、
エラーが起きた場合など、あらゆる場合にcleanup-formsが
評価されます。
body形式が普通に終る場合、unwind-protect
は
cleanup-formsを評価した後に、最後のbody形式の値を
返します。body形式が終了しなかった場合、
unwind-protect
は普通の感覚においては何も値を返しません。
実際にはbodyだけがunwind-protect
によって保護されます。
cleanup-forms自身のどこかで、非局所的な脱出が起きた場合(例えば、
throw
またはエラー)、unwind-protect
は、その後の部分の
評価を保証しません。
cleanup-formsのうちの一つの失敗に、問題を引き起こす可能性が
ある場合には、その形式を別のunwind-protect
で保護してください。
現在有効なunwind-protect
形式の個数は、ローカル変数の束縛の数とともに、
上限max-specpdl-size
に対してカウントされます
(see section ローカル変数)。
例えば、以下では一時的な利用のために不可視のバッファを作り、終る前に かならずそれを削除するようにしています。
(save-excursion (let ((buffer (get-buffer-create " *temp*"))) (set-buffer buffer) (unwind-protect body (kill-buffer buffer))))
(kill-buffer (current-buffer))
と書き、変数buffer
なし
ですますことができると考えるかもしれません。しかし、
違うバッファに切り替わった後にbodyがエラーを受け取ることが
起こり得るならば、上に示した方法の方が安全です! (代わりに、
本体のまわりに別のsave-excursion
を書き、一時的なバッファ
が、それを削除するときにカレントになることを保証するようにすることが
できます。)
以下はファイル`ftp.el'から取ってきた実際の例です。
それはリモート・マシンへの接続を設定しようとするプロセスを
作ります(see section プロセス)。関数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)))))
この例は実際には小さなバグを持っています。ユーザがとりやめようと
してC-gをタイプし、関数ftp-setup-buffer
から戻った
直後で、変数process
が設定される前に中断が起きると、
そのプロセスは削除されません。このバグを修正する容易な方法は
ありませんが、とにかく、それは非常にありそうもないことです。
以下は一時的なバッファを必ず削除するようにするためにunwind-protect
を使用する別の例です。この例ではunwind-protect
によって返却
される値が使用されています。
(defun shell-command-string (cmd) "Return the output of the shell command CMD, as a string." (save-excursion (set-buffer (generate-new-buffer " OS*cmd")) (shell-command cmd t) (unwind-protect (buffer-string) (kill-buffer (current-buffer)))))
Go to the first, previous, next, last section, table of contents.