(導入用のテキスト!!)
--------
CN: 別のほとんどのバージョンの Lisp では、様々な exiting 用のフォームを用いる ことができます。return, return-from, go 等。 GNU Emacs は throw と signal のみ を持っています。
--------
Special Form: catch tag forms*
この特殊フォームは、(tag と呼ばれる) 特別なマーカーをスタック上に置き、 forms から (途中の関数を扱わず) 直接、この catch に戻ることができるように します。
catch は、まず (どんな Lisp のオブジェクトでも返しうる) tag を評価します。 タグ(訳注:tag) は、(そうでなくてもよいのですが) 一般的にはシンボルです。 次に forms 中のものを順に評価します。 forms 中のものが、この tag への throw を実行しなかった場合、最後のフォームの返す値を catch の値として返し ます。
Function: throw tag value
この関数は、catch に対応するフォームです。 tag と eq である (catch で確立 された)タグが存在する場合、(すぐに)制御をその catch フォームに移し、catch は value を返します。
(スタック上に置かれた) 途中の関数呼び出しとローカル変数のバインディングを (全て)削除します。 unwind-protect のクリーンアップを行ない、save- excursion, save-window-excursion, save-restriction でセーブされたコンテキ ストをリストアします。
スタック上にマッチするタグが存在しない場合、no-catch エラーを出し(訳注: signal)ます。
throw は、(tag にマッチするタグを求め) スタックをサーチするため、途中に存 在する catch 起動は別のタグで exit されることになり、無視されることになり ます。スタック上に同じ(訳注:identical)タグが複数存在する場合、 throw に最 も近いもの(i.e., 最も最近に入った catch) を選びます。
次に catch と throw を用いる(簡単な)例を示します。
(setq x 0) => 0 (catch 'tab1 (setq x (1+ x)) ; x は 1
(if (= x 2) (throw 'tag1 'two)) ; throw は実行されない (list x)) => (1) (catch 'tab1 (setq x (1+ x)) ; x は 2 (if (= x 2) (throw 'tag1 'two)) ; throw が実行される (list x)) => two
以下に示したより複雑な例では、スタック上にタグを 2 つ置きます。最初の例は tag2 という名前のタグを 2 つスタック上に持ち、throw はその最も近い方に行き ます(タグは関数 catch2 中の catch で置かれます)。この(呼び出しの)フォーム は catch2 を呼んだ結果 (yes) を print し、フォームの評価を続け、その 2 番 目のフォームの値 (no) を返します。
2 番目の例もタグを 2 つスタック上に置きます。浅い方が tag2 で、深い方が tag3 です。 throw は tag2 に throw します。これは catch2 で (タグ tag3 と して) 置かれた catcher(訳注:catch するもの) をバイパスします。 throw は (かわりに)その catcher の外の tag2 に行き、値 yes を返し、 print を実行し たり 'no を評価したりはしません。
catch2 がどのように呼ばれようとも、関数 catch2 中の最後のフォーム (setq x 1) が評価されることはありません。
(defun catch2 (tag) (catch tag (setq x 0) (throw 'tag2 'yes) (setq x 1))) => catch2 (catch 'tag2 (print (catch2 'tag2)) 'no) ;最初のもの -> yes => no (catch 'tag2 (print (catch2 'tag3)) 'no) ;2 番目のもの => yes
Special Form: unwind-protect protected-form cleanup-forms*
この特殊フォームは、 protected-form をどのように exit しようとも cleanup- form を実行することを保証します。これは "ある特定の処理(訳注:computation) の後、ある一連の処理(訳注:task) を終了する" ことが本質的である場合に用い ます。 protected-form がその unwind-protect を越える throw を実行したり、 エラーを生じた場合も、 cleanup-forms を評価します(cleanup-forms がエラーを 起こしたら??)。
(役にはたたないかもしれませんが)簡単な例として、以下に示すコードを参照して 下さい。 throw は throw された値を catch が返すようにしますが、 (unwind- protect から出る際)クリーンアップフォーム (print (setq x 0)) を評価しま す。x は 0 に設定され、それが print されます。
(throw にマッチする catch が存在しない) 2 番目の例において、処理結果はエ ラーになりますが、クリーンアップフォーム (print (setq x 0)) は評価されま す。
(catch 'tag (unwind-protect (progn (setq x 1) (throw 'tag 'thrown)) (print (setq x 0)) (sit-for 1))) -> 0 ; 待つ(訳注:pause) => thrown
(unwind-protect (progn (setq x 1) (throw 'tag 'thrown)) (print (setq x 0)) (sit-for 1))) -> 0 ; 待つ(訳注:pause) => ERROR: No catch for tag: tag, thrown
より現実的な例として、ファイル `ftp.el' から取った次の例を参照して下さい。 ftp のログインが失敗した場合、 (この関数の前の部分で作った)プロセスを削除 する必要があります。関数 ftp-login は、関数の作者の予期できない (多くの)問
題に直面する確率が高いため、どのようなエラー (訳注:failure) に対してもプ ロセスの削除を保証するフォームで守られています。
(let ((win nil)) (unwind-protect (if (setq win (ftp-login process host user password)) (message "Logged in") (error "Ftp login lost")) (or win (delete-process process))))
(実際には)フォーム protected-form のみが unwind-protect で守られます。普通 でない形(e.g. throw や error) でクリーンアップフォームのどれかを exit した 場合、その後の部分の実行は保証されません。クリーンアップフォームのいずれか が失敗する可能性を持つ場合、また別の unwind-protect で守られてなくてはいけ ません。
次の例では、関数 complex-kill はエラーを生じうるものと考えられており、どん なことがあっても両方のプロセスの削除を保証しようとしています。
(let ((proca nil) (procb nil)) (unwind-protect (create-and-run proca procb) (unwind-protect (complex-kill proca) (delete-process proca) (delete-process procb))))