マクロの使い方覚え書き。
まず、「プログラミングClojure」に出ていたノーマルな定義。
unlessというマクロ。
(defmacro unless [expr form] (list 'if expr nil form))
実行結果
t-macro.core=> (unless false (println "1")) 1 nil t-macro.core=> (unless true (println "1")) nil
Clojureのマクロは、Cのプリプロセッサと同様に、 定義された内容はいったん展開され、その後式として評価される。
(macroexpand-1)
を使うと展開した後の式が見える。
t-macro.core=> (macroexpand-1 '(unless true (println "1"))) (if true nil (println "1"))
マクロの中身は(list 'if expr nil form)
であるが、
このうちexpr
とform
は引数として渡され、評価されずにそのまま展開される。
一方、(list ...)
は評価されて、これがマクロの結果として返される。
'if
はリーダマクロ'
を使ってifを評価せずにそのまま渡している。
nilは評価されるがnilになる。
ここがポイントのようで、Cのプリプロの単純な文字列置換とはちょっと違う。
マクロを以下のように書き換えてみる。unless-2とする。
(defmacro unless-2 [expr form] (if expr nil form))
さっきとの違いは、(list...)
を外し、'if
ではなくif
を使った。
実行してみる。
t-macro.core=> (unless-2 false (println "1")) 1 nil t-macro.core=> (unless-2 true (println "1")) nil
一見、結果は一緒。
しかし、(macroexpand-1)
すると違いが分かる。
t-macro.core=> (macroexpand '(unless-2 false (print "1"))) (print "1")
マクロとして展開された結果が違う。
今度のはif
がマクロ展開時に実行されたので、その結果がマクロ展開の結果として返っている。
上記を見ると、マクロ定義は簡単に言えば 何かしらのClojureプログラムのリストを返す関数を作ること、と思えばよさそうだ。
とすると、さきほど一見、unless
とunless-2
の実行結果が同じに見えたが、
以下を試してみると、、、
t-macro.core=> (unless (= 1 2) (println "1")) 1 nil t-macro.core=> (unless-2 (= 1 2) (println "1")) nil
結果は違ってくる。 macroexpand-1してみる。
t-macro.core=> (macroexpand-1 '(unless (= 1 2) (println "1"))) (if (= 1 2) nil (println "1")) t-macro.core=> (macroexpand-1 '(unless-2 (= 1 2) (println "1"))) nil
unless-2はなぜnilを返すのだろう?
unless-2はマクロとしては、(if '(= 1 2) nil '(println "1"))
を実行する形になる。
t-macro.core=> (if '(= 1 1) "yes" "no") "yes" t-macro.core=> (if '(= 1 2) "yes" "no") "yes"
(if..)
関数は、評価なしのformをそのまま渡すと true として見なすようだ。
なお、
t-macro.core=> 'true true t-macro.core=> 'fase fase
で、true, falseをquoteしてもtrue, falseになるようだ。 これ忘れるとハマるな。