On Lisp -> Clojure へ移植: 6.1~6.3
6 マクロ
6.1 マクロはどのように動作するか
p54
(defmacro nil! [x] (list 'def x nil))
6.2 逆クォート
そのままバッククォートと言われたりもする。
一応補足すると、Common Lisp でのバッククォート中のカンマは Clojure では ~
に相当する。
同様に Common Lisp の ,@
は Clojure では ~@
となる。
対比して書くとこんな感じ。
項目 | Common Lisp | Clojure |
---|---|---|
クォート | ' | ' |
バッククォート | ` | ` |
アンクォート | , | ~ |
スプライシングアンクォート | ,@ | ~ |
アンクォートの文字が違うだけですな。
p56
バッククォート版 nil!
(defmacro nil! [x] `(def ~x nil))
p57
図 29 マクロ定義に逆クォートを使った例と使わない例.
(defmacro nif [expr pos zero neg] `(cond (pos? ~expr) ~pos (zero? ~expr) ~zero (neg? ~expr) ~neg)) (defmacro nif [expr pos zero neg] (list 'cond (list 'pos? expr) pos (list 'zero? expr) zero (list 'neg? expr) neg))
Common Lisp の truncate や signum と全く同じものはないので別実装。
以下テストコード。
(map #(nif % 'p 'z 'n) [0 2.5 -8]) ;=> (z p n)
when。どうでもいいんだけど Clojure の when はなぜか逆クォートを使わない版で書かれてる。
(defmacro our-when [test & body] `(if ~test (do ~@body)))
p58
(defn greet [name] `(hello ~name))
少し脱線
一つ Clojure で注意する点があるとすると バッククォート中でクォートされてないシンボルがあった場合まず名前解決しようとする。 何気なく定義した上の関数の結果を見るとそれが分かる。
(greet 'Clojure) ;=> (user/hello Clojure)
動きとしてはまず既に定義済みの名前がないか探し、なければ現在の名前空間で修飾して 展開するようだ。この展開された式を評価してみても user 名前空間に hello なんてない よと怒られる。
既に定義済みの関数を使ったものをバッククォート中に含めてみる。
(defn mac-factorial [n] `(* ~@(range 1 (inc n)))) (mac-factorial 10) ;=> (clojure.core/* 1 2 3 4 5 6 7 8 9 10)
も一つ Clojure 特有と言うわけじゃないんだろうが defn と defmacro の違いは defn は定義した式を評価すると言うのに対して defmacro は展開できるところまで 式を展開した後に評価すると言う点が違うと言う事でいいのかな。
先と中身が全く同じものを defmacro で定義して評価してみる。
(defmacro mac-factorial [n] `(* ~@(range 1 (inc n)))) (mac-factorial 10) ;=> 3628800
6.3 単純なマクロの定義
member の定義だが、同等の事は Clojure なら drop-while で可能。
(defn member [item col & {:keys [test] :or {test =}}] (seq (drop-while #(not (test item %)) col)))
Clojure では nil と () が違うものなので一応 seq をかましておく。 Common Lisp 標準だとキーワード引数として他にも取れるものがあるのだが ここでは省略する。
また、Common Lisp の同一性チェックは案外色々あって面倒なので、 eql は Clojure の =, eq は Clojure の identical? と置き換えたものとして memq 定義をする。
p59
(defmacro memq [x choices] `(member ~x ~choices :test identical?))
それと本書の方では #' が出てるが Clojure のそれとは異なり、 Common Lisp では関数呼び出しをするリードマクロな点に注意するくらいか。 これは Common Lisp では言語仕様として変数と関数の名前空間が異なる事に起因する。 他の Lisp との違いについては Clojure 公式の下記 URL に説明がある。
次に while マクロの例があるが、これまた Common Lisp の do はマクロでありそのままのものはない。 が、while は Clojure なら条件付きの loop として展開させてやれば良い。
(defmacro while [test & body] `(loop [] (if ~test (do ~@body (recur)))))
後で Clojure の方のソース見て when の存在を忘れてた事に気づいたw
今日はここまで。