Posted 2019-10-07 21:08:30 GMT

Common Lispでは、実行時、コンパイル時、リード時、その他色々なタイミングでの評価を活用しますが、その制御に専ら使われるのが、 eval-when です。

といっても、大抵 eval-when を使わないか、 (:compile-toplevel :execute :load-toplevel) を全部付けるかです。

実際の所は全部盛りを知っていれば問題ないのですが、入れ子になった場合や、全部盛り以外の組み合わせの挙動を確認してみようかなと思います。

指定の組み合わせを眺めてみる

こんな感じのコードで、適当なファイルに組み合わせを書き出します。

( setf ( logical-pathname-translations "tem" ) ' ( ( "**;*.*.*" "/tmp/**/*.*" ) ) ) (with-open-file (*standard-output* "tem:ew.lisp" :direction :output :if-does-not-exist :create :if-exists :supersede) (pprint (cons 'progn (loop :for w :in '((progn) (eval-when (:execute)) (eval-when (:compile-toplevel)) (eval-when (:load-toplevel))) :collect `(,@w (eval-when (:compile-toplevel :execute :load-toplevel) (prin1 ',w) (terpri)) ,@(loop :for i :from 0 :for x :in '(nil (:compile-toplevel) (:compile-toplevel :load-toplevel) (:load-toplevel) (:compile-toplevel :execute) (:compile-toplevel :execute :load-toplevel) (:execute) (:execute :load-toplevel)) :collect `(eval-when ,x (prin1 '(,i ,x)) (terpri))))))))

書き出した内容

( progn ( progn ( eval-when ( :compile-toplevel :execute :load-toplevel ) ( prin1 ' ( progn ) ) ( terpri ) ) ( eval-when nil ( prin1 ' ( 0 nil ) ) ( terpri ) ) ( eval-when ( :compile-toplevel ) ( prin1 ' ( 1 ( :compile-toplevel ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :load-toplevel ) ( prin1 ' ( 2 ( :compile-toplevel :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :load-toplevel ) ( prin1 ' ( 3 ( :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :execute ) ( prin1 ' ( 4 ( :compile-toplevel :execute ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :execute :load-toplevel ) ( prin1 ' ( 5 ( :compile-toplevel :execute :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :execute ) ( prin1 ' ( 6 ( :execute ) ) ) ( terpri ) ) ( eval-when ( :execute :load-toplevel ) ( prin1 ' ( 7 ( :execute :load-toplevel ) ) ) ( terpri ) ) ) ( eval-when ( :execute ) ( eval-when ( :compile-toplevel :execute :load-toplevel ) ( prin1 ' ( eval-when ( :execute ) ) ) ( terpri ) ) ( eval-when nil ( prin1 ' ( 0 nil ) ) ( terpri ) ) ( eval-when ( :compile-toplevel ) ( prin1 ' ( 1 ( :compile-toplevel ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :load-toplevel ) ( prin1 ' ( 2 ( :compile-toplevel :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :load-toplevel ) ( prin1 ' ( 3 ( :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :execute ) ( prin1 ' ( 4 ( :compile-toplevel :execute ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :execute :load-toplevel ) ( prin1 ' ( 5 ( :compile-toplevel :execute :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :execute ) ( prin1 ' ( 6 ( :execute ) ) ) ( terpri ) ) ( eval-when ( :execute :load-toplevel ) ( prin1 ' ( 7 ( :execute :load-toplevel ) ) ) ( terpri ) ) ) ( eval-when ( :compile-toplevel ) ( eval-when ( :compile-toplevel :execute :load-toplevel ) ( prin1 ' ( eval-when ( :compile-toplevel ) ) ) ( terpri ) ) ( eval-when nil ( prin1 ' ( 0 nil ) ) ( terpri ) ) ( eval-when ( :compile-toplevel ) ( prin1 ' ( 1 ( :compile-toplevel ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :load-toplevel ) ( prin1 ' ( 2 ( :compile-toplevel :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :load-toplevel ) ( prin1 ' ( 3 ( :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :execute ) ( prin1 ' ( 4 ( :compile-toplevel :execute ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :execute :load-toplevel ) ( prin1 ' ( 5 ( :compile-toplevel :execute :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :execute ) ( prin1 ' ( 6 ( :execute ) ) ) ( terpri ) ) ( eval-when ( :execute :load-toplevel ) ( prin1 ' ( 7 ( :execute :load-toplevel ) ) ) ( terpri ) ) ) ( eval-when ( :load-toplevel ) ( eval-when ( :compile-toplevel :execute :load-toplevel ) ( prin1 ' ( eval-when ( :load-toplevel ) ) ) ( terpri ) ) ( eval-when nil ( prin1 ' ( 0 nil ) ) ( terpri ) ) ( eval-when ( :compile-toplevel ) ( prin1 ' ( 1 ( :compile-toplevel ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :load-toplevel ) ( prin1 ' ( 2 ( :compile-toplevel :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :load-toplevel ) ( prin1 ' ( 3 ( :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :execute ) ( prin1 ' ( 4 ( :compile-toplevel :execute ) ) ) ( terpri ) ) ( eval-when ( :compile-toplevel :execute :load-toplevel ) ( prin1 ' ( 5 ( :compile-toplevel :execute :load-toplevel ) ) ) ( terpri ) ) ( eval-when ( :execute ) ( prin1 ' ( 6 ( :execute ) ) ) ( terpri ) ) ( eval-when ( :execute :load-toplevel ) ( prin1 ' ( 7 ( :execute :load-toplevel ) ) ) ( terpri ) ) ) )

書き出したコードを実際にコンパイルしたりロードしたりで実行してみます。

( progn ( format T "~2&================ :execute~%" ) ( load "tem:ew.lisp" :verbose nil ) ( format T "~2&================ :compile-toplevel~%" ) ( compile-file "tem:ew.lisp" :verbose nil :print nil ) ( format T "~2&================ :load-toplevel~%" ) ( load "tem:ew" :verbose nil :print nil ) )

結果の確認

上記の結果を評価タイミングごとに眺めていきます。

なお、 -toplevel と付いていることからも想像できるように、 :compile- と load- はトップレベルに置かれないと評価されません。

また、 eval-when の中はトップレベルなので、入れ子にしてもトップレベル扱いです。

:execute

executeは、実行時の評価です。

式を eval したり、コンパイルしていないソースファイルを load した場合のフェイズといえるでしょう。

( progn ) ( 4 ( :compile-toplevel :execute ) ) ( 5 ( :compile-toplevel :execute :load-toplevel ) ) ( 6 ( :execute ) ) ( 7 ( :execute :load-toplevel ) ) ================ :execute (eval-when (:execute)) (4 (:compile-toplevel :execute)) (5 (:compile-toplevel :execute :load-toplevel)) (6 (:execute)) (7 (:execute :load-toplevel))

トップレベルの式、もしくは :execute が含まれた eval-when の中だけ評価されているのが分かります。

:compile-toplevel

:compile-toplevel は、コンパイル時です。 eval-when の直下のフォームと入れ子になった :execute が評価されます。

ややこしいのが、コンパイル時には、 eval-when の :load-toplevel 指定の中身も見る(=コンパイルする)ことですが、中身は見ますが、内側に :compile-toplevel を指定しないとコンパイル時には評価されません。

( progn ) ( 1 ( :compile-toplevel ) ) ( 2 ( :compile-toplevel :load-toplevel ) ) ( 4 ( :compile-toplevel :execute ) ) ( 5 ( :compile-toplevel :execute :load-toplevel ) ) ================ :compile-toplevel (eval-when (:compile-toplevel)) (4 (:compile-toplevel :execute)) (5 (:compile-toplevel :execute :load-toplevel)) (6 (:execute)) (7 (:execute :load-toplevel)) (eval-when (:load-toplevel)) (1 (:compile-toplevel)) (2 (:compile-toplevel :load-toplevel)) (4 (:compile-toplevel :execute)) (5 (:compile-toplevel :execute :load-toplevel))

:load-toplevel

:load-toplevel は、コンパイル済みのファイルであるfaslをロードした場合の評価フェイズです。

ロードというと色々ややこしいので、以降、fasloadと呼びます。

fasloadの場合は、 :load-toplevel を入れ子にすれば、 :load-toplevel の中は評価しますが、 :execute の中身はみません。

上述のように :compile-toplevel は入れ子にしても機能しますが、それはコンパイル時に評価されるものなのでfasload時には評価されません。

( progn ) ( 2 ( :compile-toplevel :load-toplevel ) ) ( 3 ( :load-toplevel ) ) ( 5 ( :compile-toplevel :execute :load-toplevel ) ) ( 7 ( :execute :load-toplevel ) ) ================ :load-toplevel (eval-when (:load-toplevel)) (2 (:compile-toplevel :load-toplevel)) (3 (:load-toplevel)) (5 (:compile-toplevel :execute :load-toplevel)) (7 (:execute :load-toplevel))

応用の考察

マクロ展開時限定で何かを評価するには

マクロはコンパイル時に展開されますが、実行時でも展開される可能性はある(インタプリタ動作の場合)ので下記のようになるでしょうか。

fasloadではコンパイル済みの筈なので、マクロ展開が起きることはありません。

( eval-when ( :compile-toplevel :execute ) .... )

マクロ展開時限定で何かしたいことがあれば……ですが。

defpackage のシンボル汚染問題を解消する

defpackage 展開用のパッケージを作成して、コンパイル時のみの評価とすれば、fasload時には展開用のパッケージは存在しなくても良いことになります。

( in-package :cl-user ) (eval-when (:compile-toplevel) (defpackage "bfa90b48-5531-5245-9256-8dfb8d9119f3" (:use :cl)) (in-package "bfa90b48-5531-5245-9256-8dfb8d9119f3")) (defpackage foo (:use cl) (:intern a b c))

( compile-file "tem:zzz" ) ( delete-package "bfa90b48-5531-5245-9256-8dfb8d9119f3" ) ( load "tem:zzz" ) (list (find-symbol "A" :cl-user) (find-symbol "B" :cl-user) (find-symbol "C" :cl-user) (find-symbol "A" :foo)) → (nil nil nil foo::a)

良く考えれば、コンパイル時に defpackage によって使われたシンボルも、別のイメージにfasloadした時には居なくても良いので、 cl-user で書いたのと大した違いはないですね。

そう考えると、 defpackage のシンボル汚染問題もコンパイル時のイメージ限定なのかなと。

まとめ

はまり所としては、

:load-toplevel の中の :compile-toplevel がコンパイル時に評価されるというのがややこしい

の中の がコンパイル時に評価されるというのがややこしい load という関数名と、 :load-toplevel という名前が誤解を招く load で lisp ファイルを読み込めば :execute load で fasl ファイルを読み込めば :load-toplevel

という関数名と、 という名前が誤解を招く

位でしょうか。

昔のLispでは、faslを読むのには fasload という専用関数が使われ、コンパイルしていないファイルには load を使ったりしていたようですが、Common Lispで load に一本化されたようですね。

以上、 eval-when の考察でした。

■

