Special Operator EVAL-WHEN
eval-when (situation*) form* => result*
situation - 次のシンボルのうちのひとつ、 :compile-toplevel, :load-toplevel, :execute, compile, load, eval。
ただしeval, compile, loadの使用は非推奨です。
form - 暗黙のprogn
result - もしformが実行されたときはその値を、それ以外はnil。
eval-whenフォームのボディ部は、 situationsのリスト下にあるときのみ暗黙のprognとして処理します。
compile-fileで処理しているコード内で eval-whenがトップレベルフォームとして現れたとき、 situationに:compile-toplevel(あるいはcompile)と :load-toplevel(あるいはload)を使用することで 評価を行うかどうかを制御できます。 3.2.3. ファイルのコンパイルをご確認ください。
situationに:execute(あるいはeval)を使用することで eval-whenのその他のフォームの評価を行うかどうかを制御できます。 これは、それらがトップレベルフォームのものではないか、 あるいはコードがevalかcompileによって処理されたときです。 もし:executeのsituationにそのようなフォームが指定されたとき、 formのボディ部は暗黙のprognとして処理され、 それ以外の場合はeval-whenフォームはnilを返却します。
eval-whenは通常トップレベルフォームに現れますが、 それが非トップレベルフォームとして現れても意味があります。 しかし、3.2. コンパイルに記載されているコンパイル時の副作用は、 ただeval-whenがトップレベルフォームに現れたときのみ行われます。
eval-whenのひとつの使用例として、 コンパイラーがファイルを適切に読み込めるように、 ユーザーが定義したリーダーマクロを使う方法を示します。 下記のように記載する必要があります。
(eval-when (:compile-toplevel :load-toplevel :execute)
(set-macro-character #\$ #'(lambda (stream char)
(declare (ignore char))
(list 'dollar (read stream))))) => Tこれはset-macro-characterの呼び出しが コンパイラーの実行環境内での実行を引き起こし、 それによってリーダーの構文テーブルに修正が起こります。
;;; このケースではEVAL-WHENはtoplevelではなく、ただ:EXECUTEキーワード
;;; のみが考慮されます。コンパイル時においてこれらの効果はありません。
;;; ロード(もしこのLETがトップレベルのとき)か実行時(もしこのLETが
;;; 他のフォームに埋め込まれていて後にならないと呼び出されないとき)、
;;; これは(SYMBOL-FUNCTION 'FOO1)に1を返却する関数がセットされます。
(let ((x 1))
(eval-when (:execute :load-toplevel :compile-toplevel)
(setf (symbol-function 'foo1) #'(lambda () x))))
;;; もしこの式がコンパイル時にファイルのtoplevelとしてあらわれたとき、
;;; これはコンパイル時とロードに(SYMBOL-FUNCTION 'FOO2)に2を返却する
;;; 関数をセットする効果があります。
(eval-when (:execute :load-toplevel :compile-toplevel)
(let ((x 2))
(eval-when (:execute :load-toplevel :compile-toplevel)
(setf (symbol-function 'foo2) #'(lambda () x)))))
;;; もしこの式がコンパイル時にファイルのtoplevelとしてあらわれたとき、
;;; これはコンパイル時とロードに(SYMBOL-FUNCTION 'FOO2)に3を返却する
;;; 関数をセットする効果があります。
(eval-when (:execute :load-toplevel :compile-toplevel)
(setf (symbol-function 'foo3) #'(lambda () 3)))
;;; #4: これはいつでも何もしません。単純にNILを返却します。
(eval-when (:compile-toplevel)
(eval-when (:compile-toplevel)
(print 'foo4)))
;;; もしこの式がコンパイル時にファイルのtoplevelとしてあらわれたとき、
;;; コンパイル時にFOO5が印字されます。もしこのフォームが非toplevelの
;;; 位置に現れたとき、コンパイル時には何も印字されません。
;;; 文脈に関係なくロード時と実行時は何も印字されません。
(eval-when (:compile-toplevel)
(eval-when (:execute)
(print 'foo5)))
;;; もしこの式がコンパイル時にファイルのtoplevelとしてあらわれたとき、
;;; コンパイル時にFOO6が印字されます。もしこのフォームが非toplevelの
;;; 位置に現れたとき、コンパイル時には何も印字されません。
;;; 文脈に関係なくロード時と実行時は何も印字されません。
(eval-when (:execute :load-toplevel)
(eval-when (:compile-toplevel)
(print 'foo6)))なし。
なし。
eval-whenの定義により、下記の論理的な効果が示されます。
ひとつのeval-when式は、ボディ部が最大で一度実行されます。
トップレベルフォームで使用されたマクロは、 マクロ展開の中のフォームによって副作用が完了することによって出力されます。 そのマクロ展開自身には副作用は生じません。
次は悪い例。
(defmacro foo ()
(really-foo)
`(really-foo))次は良い例。
(defmacro foo ()
`(eval-when (:compile-toplevel :execute :load-toplevel) (really-foo)))この規約を守ることで、マクロが非トップレベルフォームとして現れたときに、 直感的に動作するようになります。
eval-whenの周りに配置された変数の束縛は確実にその束縛をに捕捉します。 なぜならcompile-time-tooモードは生じないからです (例えば、変数束縛の導入は、eval-whenが トップレベルフォームではないことを意味しているからです)。例えば下記の文を考えます。
(let ((x 3))
(eval-when (:execute :load-toplevel :compile-toplevel) (print x)))この文は、 実行時(例えばロード)に3を印字します。 そしてコンパイル時には何も印字しません。 defunとdefmacroの展開は、 eval-whenを実行することができて、 かつ正しくレキシカルな環境を捕捉することができるということは 重要なことです。
下記の文
(defun bar (x) (defun foo () (+ x 3)))これは、次のように展開されます。
(defun bar (x)
(progn (eval-when (:compile-toplevel)
(compiler::notice-function-definition 'foo '(x)))
(eval-when (:execute :load-toplevel)
(setf (symbol-function 'foo) #'(lambda () (+ x 3))))))この展開は、上記のルールに従うと次の同等になります。
(defun bar (x)
(setf (symbol-function 'foo) #'(lambda () (+ x 3))))これはbarの定義がトップレベルフォームのものではありません。