% Special-Operator EVAL-WHEN
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
の定義がトップレベルフォームのものではありません。