『7つの言語 7つの世界』を読みつつ Clojure 入門している。その中でマクロに関する記述があったが LISP のマクロに馴染みがないので、コードを見ても何が起きているのか瞬時にわからなかった。
正解のコードを少しずついじりながら、なぜそう書かないといけないのか、なぜそう書くと動くのかを確かめてみた。
ちなみに例は unless
で、これはマクロ入門でおなじみらしい。
失敗例
まずは挙げられているダメな例から。
(defn unless [test body] (if (not test) body)) #'user/unless (unless true (println "Lose Yourself")) Lose Yourself nil
body
が評価されてしまっている。test
が false
のとき以外は評価してほしくない。なのでこれがダメなのはわかりやすい。
正規順序でなく作用的順序で評価してしまうと無限ループが発生してしまう例が SICP にもあった(気がする)。
正しい unless
以下のようになるようだ。
(defmacro unless [test body] (list 'if (list 'not test) body))
これをいじってみる。
if
を即時評価していないのはなぜ
'if
の部分は以下のように書けるのではないだろうか?
(defmacro unless [test body] (if (list 'not test) body)) (unless true (println "Nothing to tell.")) Nothing to tell. ;; 評価されてしまっている nil (unless false (println "This is it.")) This is it. nil
はい、body
が評価されてしまっているダメ。
(macroexpand '(unless cond body)) body
上記の unless をマクロ展開してみると body
を実行するだけのマクロになってしまっていることがわかる。
not
を即時評価していないのはなぜ
では、'not
の部分は以下のように書けるのではないだろうか?
(defmacro unless [test body] (list 'if (not test) body)) (unless true (println "Nothing to tell.")) nil (unless false (println "This is it.")) This is it. nil
一見成功しているように見える…!
だがそれが命取り…!実は駄目…!
上記の unless をマクロ展開してみると条件部が既に評価されてしまっていることがわかる。
(macroexpand '(unless condition body)) (if false body) ;; 条件は false になるのに println が実行されない (unless (= 1 2) (println "This is it.")) nil
正しい unless
をマクロ展開すると条件部がまだ評価されていないことがわかる。
(macroexpand '(unless cond body)) (if (not cond) body) ;; ちゃんと unless 呼び出し時に評価されている (unless (= 1 2) (println "This is it.")) This is it. nil
defn
で書いたらどうなる?
(defn unless [test body] (list 'if (list 'not test) body)) (unless true (println "Nothing to tell.")) Nothing to tell. (if (not true) nil) (unless false (println "This is it.")) This is it. (if (not false) nil) (class (unless false (println "This is it."))) This is it. clojure.lang.PersistentList
(list 'if (list 'not test) body)
が評価されてしまった後のリストが返ってくる。body
も評価されてしまっているので println
が実行されている。
関連する関数などの知識
quote
評価してほしくないフォームに quote 関数を適用すると評価を抑制できる。
ダメなマクロの例を成り立たせるために body
を'
で遅延評価させられる。もちろん呼び出し側でコントロールしているのでよくない。
(defn unless [test body] (if (not test) body)) #'user/unless (unless true (quote (println "Lose Yourself"))) nil ;; ' 記号でも書ける (unless true '(println "Lose Yourself")) nil
defmacro
defmacro
は defn
のように関数を定義するが、その戻り値は実行時にコンパイラにマクロとして解釈されるようになる。
(doc defmacro) ------------------------- clojure.core/defmacro ([name doc-string? attr-map? [params*] body] [name doc-string? attr-map? ([params*] body) + attr-map?]) Macro Like defn, but the resulting function name is declared as a macro and will be used as a macro by the compiler when it is called. nil ;; 確かに Macro と解釈されているようす (doc unless) ------------------------- user/unless ([test body]) Macro nil nil

- 作者: Bruce A. Tate
- 出版社/メーカー: オーム社
- メディア: Kindle版
- この商品を含むブログを見る