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エラーがシグナルされます。