たとえばプログラムの将来において計算結果が不要ということがわかった場合に時間を要する計算処理を回避したい等、式の評価を遅延させると便利な場合があります。そのような遅延評価(deferred evaluation)をサポートするために、thunkライブラリは以下の関数とマクロを提供します。
formsを評価するためのthunkをリターンする(訳注:
thunkとは、別のサブルーチンに計算を追加で挿入するために使用するサブルーチンであり、計算結果が必要になるまで計算を遅延したり、別のサブルーチンの先頭や最後に処理を挿入するために使用される。英語版Wikipediaより)。thunkはthunk-delay
呼び出しのlexical環境を継承するクロージャである(クロージャーを参照)。このマクロの使用にはlexical-binding
が必要。
thunkを作成したthunk-delay
で指定されたフォームの評価をthunkに強制する。最後のフォームの評価結果をリターンする。thunkが強制されたことも“記憶”される。同一のthunkにたいする以降のthunk-force
呼び出しでは、フォームを再度評価せずに同じ結果をリターンする。
このマクロはlet
の類似だが“lazy(遅延された)”変数バインディングを作成する。すべてのバインディングは(symbol value-form)
という形式をもつ。let
とは異なり、すべてのvalue-formの評価はformsを最初に評価する際に、対応するsymbolのバインディングが使用されるまで遅延される。すべてのvalue-formは最大でも1回評価される。このマクロの使用にはlexical-binding
が必要。
例:
(defun f (number) (thunk-let ((derived-number (progn (message "Calculating 1 plus 2 times %d" number) (1+ (* 2 number))))) (if (> number 10) derived-number number)))
(f 5) ⇒ 5
(f 12) -| Calculating 1 plus 2 times 12 ⇒ 25
遅延バインドされた変数の特性として、それらにたいする(setq
による)セットはエラーになります。
これはthunk-let
と似ているが、bindings内の任意の式がこのthunk-let*
フォーム内の先行するバインディングの参照を許されている点が異なる。このマクロの使用にはlexical-binding
が必要。
(thunk-let* ((x (prog2 (message "Calculating x...") (+ 1 1) (message "Finished calculating x"))) (y (prog2 (message "Calculating y...") (+ x 1) (message "Finished calculating y"))) (z (prog2 (message "Calculating z...") (+ y 1) (message "Finished calculating z"))) (a (prog2 (message "Calculating a...") (+ z 1) (message "Finished calculating a")))) (* z x)) -| Calculating z... -| Calculating y... -| Calculating x... -| Finished calculating x -| Finished calculating y -| Finished calculating z ⇒ 8
thunk-let
とthunk-let*
はthunkを暗黙に使用します。これらの拡張はヘルパーシンボルを作成してバインディング式をラップするthunkにバインドします。forms本体中の元の変数にたいするすべての参照は、対応するヘルパー変数を引数とするthunk-force
呼び出し式に置き換えられます。したがってthunk-let
やthunk-let*
を使用するコードはthunkを使用するように書き換えが可能ですが、多くの場合には明示的にthunkを使用するよりこれらのマクロを使用するほうが優れたコードになるでしょう。