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
を探します。throw
は1番目の引数が、throw
で指定されたものとeq
であるようなcatch
を検索します。複数の該当するcatch
がある場合には、最も内側にあるものが優先されます。したがって上記の例ではthrow
がfoo
を指定していて、foo-outer
内のcatch
が同じシンボルを指定しているので、(この間に他のマッチするcatch
は存在しないと仮定するなら)そのcatch
が該当します。
throw
の実行により、マッチするcatch
までのすべてのLisp構文(関数呼び出しを含む)を脱出します。この方法によりlet
や関数呼び出しのようなバインディング構文を脱出する場合には、これらの構文を正常にexitしたときのように、そのバインディングは解消されます(ローカル変数を参照)。同様にthrow
はsave-excursion
(エクスカーションを参照)によって保存されたバッファーと位置を復元します。throw
がスペシャルフォームunwind-protect
を脱出した場合には、unwind-protect
により設定されたいくつかのクリーンアップも実行されます。
ジャンプ先となるcatch
内にレキシカル(局所的)である必要はありません。throw
はcatch
内で呼び出された別の関数から、同じようにに呼び出すことができます。throw
が行なわれたのが、時系列的にcatch
に入った後で、かつexitする前である限り、そのthrow
はcatch
にアクセスできます。エディターのコマンドループから戻るexit-recursive-edit
のようなコマンドで、throw
が使用されるのはこれが理由です。
Common Lispに関する注意: Common Lispを含む、他のほとんどのバージョンのLispは非シーケンシャルに制御を移すいくつかの方法 — たとえば
return
、return-from
、go
— をもつ。Emacs Lispはthrow
のみ。cl-libライブラリーはこれらのうちいくつかを提供する。Blocks and Exits in Common Lisp Extensionsを参照のこと。
catch
はthrow
関数にたいするリターン位置を確立する。リターン位置はtagにより、この種の他のリターン位置と区別される。tagはnil
以外の任意のLispオブジェクト。リターン位置が確立される前に、引数tagは通常どおり評価される。
リターン位置が有効な場合、catch
はbodyのフォームをテキスト順に評価する。フォームが(エラーや非ローカル脱出なしで)通常に実行されたなら、bodyの最後のフォームの値がcatch
からリターンされる。
bodyの実行の間にthrow
が実行された場合、tagと同じ値を指定するとcatch
フォームは即座にexitする。リターンされる値は、それが何であれthrow
の2番目の引数に指定された値である。
throw
の目的は、以前にcatch
により確立されたリターン位置に戻ることである。引数tagは、既存のさまざまなリターン位置からリターン位置を選択するために使用される。複数のリターン位置がtagにマッチしたら、最も内側のものが使用される。
引数valueはcatch
からリターンされる値として使用される。
タグtagのリターン位置が存在しなければ、データ(tag
value)
とともにno-catch
エラーがシグナルされます。