[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9. 制御構造

Lispプログラムは式または形式(form)(see section 形式の種類)から成っています。 私たちは、それらを制御構造(control structure)で囲むことによって、 形式の実行の順番を制御することができます。制御構造とは、 囲んでいる形式を、いつ実行するか、実行するかしないか、何回 実行するかを制御する特殊形式のことです。

最も単純な実行順序は順次実行です。最初に形式a、 次に形式bというように実行します。これは関数の本体、 またはLispコードのファイルの最上位レベルに、複数の形式を連続して 記述したときの実行であって、それらの形式は記述された順番に実行されます。 私たちはこれをテキストどおりの順序(textual order)と呼んで います。例えば、もし関数の本体が二つの形式abから 成っているなら、関数は最初にa、次にbを評価し、 bの値が関数の値になります。

明示的な制御構造は順次以外の実行順序を可能にします。

Emacs Lispは、順次実行の変種、条件分岐、繰返し、および(制御された) ジャンプを含んだ数種類の制御構造を提供していて、そのすべてを以下に説明 します。 組込みの制御構造は、その部分形式が必ずしも評価されるわけではなく、 また、順番に評価されるわけでもないので、特殊形式になっているのです。 マクロを使って、独自の制御構造を定義することができます (see section マクロ)。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.1 順次実行

形式を出現順に評価することは、ある形式から他の形式へ制御を移す方法の なかで、もっともよく使われるものです。 関数本体のような、いくつかの文脈においては自動的にこのようになります。 その他の場所で、これを行うには制御構造の構文要素を使用しなければ なりません。prognはLispの最も簡単な制御の構文要素です。

特殊形式prognは以下のようなものです。

 
(progn a b c …)

これは形式a, b, cなどを、その順番で実行することを 意味しています。これらの形式はprogn形式の本体と呼ばれます。 本体の最後の形式の値がprogn全体の値になります。

初期のLispでは、prognは、連続した二つ以上の形式を実行し、 その最後の値を使用するための唯一の方法でした。 しかし、プログラマは、(その時点では)ただ一つの形式だけが許されていた 関数の本体にも、 prognを使用することがしばしば必要であることに気づきました。 そこで、関数の本体に「暗黙のprogn」を作りました。 その中の形式は実際のprognの本体と同様に扱われます。 他の多くの制御構造も同様に暗黙のprognを含んでいます。 結果として、prognは本来必要であるほどには使用されません。 それはunwind-protect, and, or, またはifthen部分の中で最もよく使用されます。

Special Form: progn forms…

この特殊形式はformsのすべてをテキストどおりの順序で評価し、 最後の形式の結果を返します。

 
(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"

以下の二つの制御構造も同様に形式の並びを評価しますが、異なった値を 返します。

Special Form: prog1 form1 forms…

この特殊形式は、form1formsのすべてをテキストどおりの 順序で評価し、form1の結果を返します。

 
(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)))
Special Form: prog2 form1 form2 forms…

この特殊形式は、form1, form2, そしてその後の forms のすべてをテキストどおりの順序で評価し、form2の結果を返します。

 
(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 ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.2 条件分岐

条件分岐の制御構造は選択肢の中から選び出すことをします。 Emacs Lispは、二つの条件分岐の形式を持っています。 他の言語とほとんど同じであるifと、一般化されたcase文で あるcondです。

Special Form: if condition then-form else-forms…

ifconditionの値を元にthen-formelse-formsかを 選択します。 評価されたconditionnilでないならば、then-form が評価され、その結果が返されます。 そうでなければ、else-formsがテキストどおりの順序で評価され、 最後のものの値が返されます。 (ifelse部は暗黙のprognの例です。 see section 順次実行)

conditionが値nilを持ち、else-formsが与えられて いなければ、ifnilを返します。

ifは選択されない枝はまったく評価されない (無視される)ので、特殊形式になっています。 したがって、以下の例でprintは決して呼ばれないのでtrue は印字されません。

 
(if nil 
    (print 'true) 
  'very-false)
⇒ very-false
Special Form: cond clause…

condは任意個の選択肢から選択します。 condの個々のclauseはリストでなければなりません。 このリストのCARconditionです。 残りの要素は(もしあれば)body-formsです。 したがって句(clause)は次のようになります。

 
(condition body-forms…)

condは個々の句のconditionを評価していくことによって、 その句の並びをテキストどおりの順序で試します。 conditionの値がnilでなければ、その句は「成功」します。 その場合、cond はそのbody-formsを評価し、 body-formsの最後の値がcondの値となります。 残りの句は無視されます。

conditionの値がnilならば、その句は「失敗」し、 condは次の句に制御を移し、その conditionを試していきます。

すべてのconditionnilに評価された場合、すなわち、 すべての句が失敗した場合、condnilを返します。

句は次のようになっているかもしれません。

 
(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)。 形式ttに評価され、決して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 ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.3 条件を結合するための構文要素

この節では複雑な条件を表現するためにifcondとともによく使用される三つの構文要素を説明します。 構文要素andorは、ある種の複合した条件を 組み合わせるものとして別個に使用されることもあります。

Function: not condition

この関数はconditionが偽であるかをテストします。 conditionnilであればtを返し、 そうでなければnilを返します。 関数notnullと同一ですが、 空リストのテストにはnullの方を使用することを私たちは勧めます。

Special Form: and conditions…

特殊形式andは、すべてのconditionsが真であるかを テストします。それはconditionsを書かれた順序で一つずつ 評価していくことによって行なわれます。

conditionsのうちのどれかがnilに評価されれば、 残りのconditionsがどうであろうともandの 結果はnilになるはずです。だから、 andは残りのconditionsを無視して、 ただちに帰ります。

すべてのconditionsnilでなければ、その最後のものの 値が形式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))))
Special Form: or conditions…

特殊形式orは少なくともconditionsの一つが真で あるかをテストします。それは conditionsを書かれた順序で一つずつ 評価していくことによって行なわれます。

conditionsのうちのどれかがnilでないものに評価されれば、 orの結果はnil以外になるはずです。 それなので、orは残りのconditionsを無視して、 ただちに帰ります。その返す値は返却の直前に評価された条件 のnilでない値です。

すべてのconditionsnilであれば、ornilを 返します。

例えば、以下の式はxが0またはnilのどちらかである かをテストします。

 
(or (eq x nil) (eq x 0))

構文要素andのように、orcondで書き表すことが できます。例えば:

 
(or arg1 arg2 arg3)
≡
(cond (arg1)
      (arg2)
      (arg3))

ifを使ってもorとほとんど同じものを書き表すことが できますが、完全ではありません。

 
(if arg1 arg1
  (if arg2 arg2 
    arg3))

これはarg1またはarg2を2回評価するので、 完全には等しくありません。 一方、(or arg1 arg2 arg3)では、 何度も評価されることは決してありません。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.4 繰り返し

繰り返しはプログラムの一部を反復して実行することを意味します。 例えば、あなたはリストの個々の要素について一度ずつ、 または0からnまでの個々の整数について一度ずつ、 ある計算を行ないたいとします。 あなたは特殊形式whileを使用してEmacs Lispでこれを 行なうことができます。

Special Form: while condition forms…

whileは最初にconditionを評価します。 その結果がnilでなければ、テキストどおりの順序でformsを 評価します。そして、conditionを再評価し、その結果が nil でなければformsを再び評価します。 この処理はconditionnilに評価されるまで繰り返します。

繰り返しの回数には制限がありません。その繰り返しは、conditionnil に評価されるか、エラーまたは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が本体を持たず、終了テストだけ(ポイントの移動という 実際の処理も行うが)という点で、まれなものです。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.5 非局所脱出

非局所脱出(nonlocal exit)はプログラム中のある位置から、 別の離れた位置への制御の移行です。非局所脱出はEmacs Lispでは、エラーの 結果として生じることがあります。また、明示的な制御のもとで、それを使用する こともできます。非局所脱出は脱出しようとしている構文要素にある全ての 変数の束縛を解きます。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.5.1 明示的な非局所脱出: catchthrow

たいていの制御構造は構造自身内での制御の流れにのみ影響します。 関数throwは、この通常のプログラム実行の規則の例外です。 それは要求により非局所脱出を実行します (他の例外もありますが、それはエラーをハンドルするだけのためのものです)。 throwcatchの内側で使用され、そのcatchに 飛びます。例えば:

 
(catch 'foo
  (progn
    …
    (throw 'foo t)
    …))

throwは該当するcatchの直後に制御を移します (該当するcatchから直ちにリターンする)。 throw以降のコードは実行されません。 throwの2番目の引数はcatchの戻り値として使用されます。

throwcatchは最初の引数によって対応がつけられます。 throwは、最初の引数がthrowで指定されたもの とeqであるcatchを検索します。 したがって上記の例では、throwは、fooを指しており、 catchも同じシンボルを指定しているので、そのcatchが 適用されます。二つ以上の適用可能なcatchがある場合は、 最も内側のものが優先されます。

throwを実行すると、対応するcatchまでの、 すべてのLisp構文要素(関数呼出しも含む)から脱出します。 letまたは関数呼出しのような束縛をつくる構文要素から このようにして脱出した場合、 それらの構文要素が普通に終了するのと同様に、 その束縛は解除されます(see section ローカル変数)。 同様に、throwsave-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だけを持ちます。

Special Form: catch tag body…

catchthrow関数のための復帰位置を確立します。 復帰位置はtag(任意の Lisp オブジェクトを指定可能)によって 他の復帰位置と区別されます。普通は復帰位置が確立される前に、 引数tagが評価されます。

有効な復帰位置を持って、catchは、テキストどおりの順序でbody の形式を評価します。エラーまたは非局所脱出なしに、普通にその形式が 実行されるならば、bodyの最後の形式の値がcatchから 返されます。

throwが同じ値tagを指定してbodyの中で行なわれる ならば、直ちにcatchを抜けます。それが返す値は throwの2番目の引数として指定されたものです。

Function: throw tag value

throwの目的は前もってcatchによって設定された復帰 位置に戻ることです。引数tagは、いくつかの存在する復帰位置 の中から選択するために使用されます。それはcatchに指定された 値とeqにならなければなりません。複数の復帰位置がtag と一致する場合、最も内側のものが使用されます。

引数valueは、そのcatchから返す値として使用 されます。

実効的にタグtagに一致する復帰位置がない場合には、 no-catchエラーがデータ(tag value) をともなって発行されます。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.5.2 catchthrowの例

catchthrowを使用する一つの方法は2重に入れ子に なったループから抜けることです。(たいていの言語では、これは "go to" で行なわれます。) 以下は ij をそれぞれ 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でない値を返したならば、 直ちに中断して、ijのリストを返します。 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は決して評価されません。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.5.3 エラー

Emacs Lispは何かの理由によって評価できない形式を評価しようとした場合、 エラー(error)発行(signal)します。

エラーが発行された場合、Emacsのデフォルトの反応はエラーメッセージを 印字し、現在のコマンドの実行を終了することです。 これは、ほとんどの場合適切なものです。 例えば、バッファの最後でC-fをタイプするような場合です。

複雑なプログラムでは、単純な終了が望むものでは ないかもしれません。例えば、そのプログラムはデータ構造の一時的な変更を行って いるかもしれないし、プログラムが終了する前に消去されるべき、 一時的なバッファを作成しているかもしれません。そのような場合には エラーの場合に評価されるクリーンアップ式(cleanup expression)を設定する unwind-protect を使用します(see section 非局所脱出のクリーンアップ)。ときには サブルーチンでエラーがあっても、実行の継続を 望むかもしれません。このような場合には、エラーの場合に制御を回復するための エラー・ハンドラ(error handler)を設定する condition-case を 使用します。

プログラムのある場所から別の場所に制御を移すためにエラーのハンドルを 使用する、という誘惑には抵抗してください。その代わりにはcatchthrowを使用してください。See section 明示的な非局所脱出: catchthrow


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.5.3.1 エラーを発行する方法

ほとんどのエラーは、他の目的で呼び出しているLisp プリミティブ (例えば、整数のCARを取ろうとしたり、バッファの最後で前方に 移動しようとすること)によって「自動的に」発行されます。 一方、関数errorsignalによって、明示的にエラーを発行する こともできます。

とりやめ(ユーザが C-gをタイプしたときに起きること)は エラーとは考えられませんが、ほとんどエラーと同様にハンドル されます。See section とりやめ

Function: error format-string &rest args

この関数は、format (see section 文字と文字列間の変換)を format-stringargsに適用し エラー・メッセージを作り、エラーを発行します。

以下は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は、二つの引数(エラー・シンボルerrorformat によって返される文字列を含んだリスト)とともにsignalを呼び出す ことをします。

文字列をエラー・メッセージとしてそのまま使用したい場合、 (error string)とだけ書いてはいけません。string`%' を含んでいる場合、それは整形指定として解釈され、不正な結果 になってしまいます。その代わりに、(error "%s" string) としてください。

Function: signal error-symbol data

この関数はerror-symbolによって名づけられたエラーを発行します。 引数dataはエラーの状況に関連する追加のLispオブジェクトです。

引数error-symbolは、エラー・シンボル(error symbol) (値が条件名のリストである属性error-conditionsをもつシンボル)で なければなりません。これが、Emacs Lispが種々のエラーを分類する やり方です。

data中のオブジェクトの数と意味はerror-symbolに依存 します。例えば、wrong-type-argエラーでは、リストに二つの オブジェクトを入れます。期待されている型を記述する述語と、 その型との適合に失敗したオブジェクトです。エラー・シンボルの説明は、 See section エラー・シンボルと条件名

error-symboldataの両方を、エラーをハンドルする任意の エラー・ハンドラは利用できます。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エラー の概念に相当するものはありません。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.5.3.2 Emacsがエラーをハンドルする方法

エラーが発行された場合、signalは、そのエラーについて有効な ハンドラ(handler)を探します。ハンドラは、Lispプログラムの一部で エラーが起きた場合に実行されるように指定されたLisp式のならびです。 そのエラーが適切なハンドラを持っていれば、そのハンドラが実行され、 そのハンドラの後から、制御が再開されます。そのハンドラは、それを、 設定されたcondition-caseの環境で実行されます。 condition-caseで呼ばれるすべての関数は、すでに脱出しているので、 ハンドラはそれらに戻れません。

そのエラーに対して適切なハンドラがなければ、現在のコマンドは終了させられ、 制御はエディタのコマンド・ループに戻ります。コマンド・ループには、 あらゆる種類のエラーを扱う暗黙のハンドラがあるからです。 コマンド・ループのハンドラはそのエラー・シンボルと、エラー・メッセージ の印字に関連したデータを使用します。

明示的なハンドラを持たないエラーはLispデバッガを呼び出すことが できます。デバッガは変数debug-on-error (see section エラーでデバッガにはいる) がnilでない場合に有効にされます。エラー・ハンドラとは違い、 デバッガはエラーの環境で動作するので、あなたはエラーの時点 での変数の値を正確に調べることができます。


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.5.3.3 エラーをハンドルするコードの記述

エラーを発行することの通常の効果は動作しているコマンドを終了し、 直ちにEmacsエディタのコマンド・ループに戻ることです。 特殊形式condition-caseでエラー・ハンドラを設定すると、 プログラムのある部分で起こるエラーを捕捉するようにすることが できます。以下に簡単な例を示します。

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

これは、filenameという名前のファイルを消去し、 どんなエラーでもそれが起きたときには捕捉し、nilを返します。

condition-caseの2番目の引数は保護形式(protected form)です (先の例での保護形式はdelete-fileを呼び出すことです)。 この形式の実行が始まるときにエラー・ハンドラが有効になり、 この形式が戻るときに無効化されます。それは介在する時間のすべてにおいて 有効状態にあります。特に、この形式、そのサブルーチン、その他 によって呼び出される関数の実行中には有効状態にあります。 厳密な話、エラーは保護形式によって呼び出されるLispプリミティブ (signalerrorを含む)によって発行されるのであって、 保護形式自身によって発行されるものではないので、これはよいことです。

引数の中で、保護形式の後にはハンドラが続きます。個々のハンドラには、 ハンドルするエラーを指定する、一つ以上の(シンボルである) 状態名(condition name)のリストを置きます。エラーを発行するときに 指定するエラーシンボルも、状態名のリストを定義します。それらが共通の 状態名をもつならば、そのハンドラがエラーに適合します。先の例では 一つのハンドラがあり、それはすべてのエラーをカバーする一つの 状態名errorを指定しています。

適用可能なハンドラの探索は最も最近に設定されたハンドラから始め、すべての 設定されたハンドラをチェックします。したがって、二重にネストしている condition-case形式が同じエラーをハンドルするようになっている 場合には、二つのうちの内側のものが実際にそれをハンドルすることになります。

エラーがハンドルされるときには、制御がハンドラに渡ります。これが起こる前に、 Emacsは抜けようとしている束縛構文要素によって束縛されている全ての変数の束縛を 解除し、抜けたすべてのunwind-protect形式のクリーンアップを実行 します。いったん、制御がハンドラに到達すると、そのハンドラの本体が実行 されます。

ハンドラの本体の実行の後、実行はcondition-case形式から 戻ることで続行します。保護形式はハンドラの実行前に完全に抜けて いるので、ハンドラはエラー時点の実行を再開することはできず、 保護形式で束縛していた変数を調べることもできません。行えることの すべてはクリーンアップと続行です。

condition-case は、insert-file-contents の呼び出しで ファイルのオープンに失敗するといった、予想できるエラーを捕捉するのに よく使用されます。また、プログラムがユーザから読み込んだ式を評価する ときに起きるような、まったく予測不可能なエラーを捕捉するのにも 使用されます。

エラーの発行とハンドルは、throwcatchと類似している点が ありますが、まったく異なった機能です。エラーは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はハンドルされるエラーの状態名、 または状態名のリストです。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-caseprotected-form を実行しているとき、この変数を束縛せず、エラーをハンドルするときにのみ 束縛します。その時点では変数varを形式 (error-symbol . data)に局所的に束縛し、エラーの詳細 を与えます。ハンドラは何を行うかを決定するために、このリストを 参照することができます。例えば、エラーが、ファイルのオープンに失敗 したというものであれば、ファイル名はdataの2番目の要素 (var の3番目の要素)になっています。

varnilならば、変数を束縛する手段はありません。 ハンドラは、エラー・シンボルおよび関連するデータを使えません。

以下は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

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.5.3.4 エラー・シンボルと条件名

エラーを発行するときには、あなたが念頭に置いているエラーの種類を 示すエラー・シンボル(error symbol)を指定します。個々のエラーは、 それを分類するために、唯一無二のエラー・シンボルを持っています。これは Emacs Lisp言語で定義された、最も優れたエラーの分類法です。

この狭い分類法は 状態名(condition name)で識別されるエラー状態(error condition) と呼ばれる、より幅のある種別の階層にグループ化されています。そのような 種別の中で最も狭いものが、エラー・シンボルそのものです。個々の エラー・シンボルは状態名でもあります。より広い種別の状態名もあり、 それはあらゆる種類のエラーをとる状態名errorにまで至ります。 したがって、個々のエラーは一つ以上の状態名を持っています。 すなわち、errorerrorからより明瞭に する場合のエラー・シンボル、そして場合によっては中間の分類によるもの。

シンボルがエラー・シンボルであるためには、状態名のリストを 与える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-errormy-own-errorsに分類される 任意のエラーをハンドルします。

 
(condition-case foo
    (bar nil t)
  (my-own-errors nil))

エラーを分類する有意義な方法は、状態名(エラーをハンドラと一致させる ために使用する名前)によることです。エラーシンボルは意図した エラー・メッセージと状態名のリストとを指定するのに 便利な方法としてのみ役立ちます。一つのエラー・シンボルではなく、 状態名のリストをsignalに与えるのは面倒なことです。

逆に、状態名ではなく、エラー・シンボルを使用することは condition-caseの能力を明らかに減少させます。 状態名は、あなたがエラー・ハンドラを書くときに、普遍性のあるさまざまなレベルに エラーを分類することを可能にします。エラー・シンボル単独で使用することは 最も狭いレベルの分類以外をすべてふるい落します。

すべての標準のエラー・シンボルとその状態の一覧は、See section 標準のエラー


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

9.5.4 非局所脱出のクリーンアップ

構文要素unwind-protectは、一時的に、あるデータ構造を一貫性のない 状態に置くときには常に必須となります。つまり、エラーまたはthrow の場合に、データが一貫していることを保証することができます。

Special Form: unwind-protect body cleanup-forms…

unwind-protectは、 制御がbodyから離れた場合、そこでどのようなことが起ころうとも cleanup-formsが評価される、という保証の上でbodyを実行 します。bodyが普通に完了する場合、 unwind-protectの外にthrowが実行される場合、 エラーが起きた場合など、あらゆる場合にcleanup-formsが 評価されます。

body形式が普通に終る場合、unwind-protectcleanup-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)))))

[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Yasutaka SHINDOH on September, 29 2006 using texi2html 1.76.