続・defmacro!

defmacro!を知って以来、defmacro!なしにマクロを書けない体になってしまいました。
Let Over Lambdaは体に毒です。注意しましょう。
実際にdefmacro!を使うようになって気になったことを二つほど。
*なぜprogn*
Let Over Lambdaでのdefmacro!の定義は次のようになっています。

(defmacro defmacro! (name args &rest body)
(let* ((os (remove-if-not #'o!-symbol-p args))
(gs (mapcar #'o!-symbol-to-g!-symbol os)))
`(defmacro/g! ,name ,args
`(let ,(mapcar #'list (list ,@gs) (list ,@os))
,(progn ,@body)))))

この定義を見て気になったのが最後の行のprognです。
続くのが ,@body だけなら、わざわざprognで囲まなくても、
,,@body
とだけ書けば充分じゃないのかと思い、そう書き換えてみました。
書き換えて使ってもまったく問題が無かったのでそのままにしていたんですが、
ある時にそのprognの意味を知らされました。

(defmacro! hoge (&rest args)
(unless (= (mod (length args) 2) 0)
(error "odd number of arguments"))
...))

prognがない場合、unlessが返すNILが取り残され、
(hoge 1 2 3 4) などの展開形が次のようになってしまいます。

(let (...)
NIL
...)

何の説明もなく、おまけのように付属しているprognが、
実は意味を持っていたというのはやられました。
*引数の分配に対応してない*
勝手に取り外したprognを付けなおし、
定義どおりのdefmacro!を使っていたら、
今度は次のようなコードを書いた際にコンパイルエラーが発生しました。

(defmacro! foo ((o!bar &optional ...) &rest rest)
...)

defmacro!の定義を見直してみれば一目瞭然。
(remove-if-not #’o!-symbol-p args)
と、argsが平らであるのが前提なコードが書いてあるので、
(flatten args) という風にリストを平坦化することで対応しました。

ああ、それから去年の夏頃に「CLでwikiをつくってるよー」と言ってましたが、
最近もちょびちょび触っています。夏の時点で一通りの機能を持っていたんですが、
その頃のCommon Lispの知識が余りに乏しかったため、
汚いコードや無意味なコード(もともとある関数を自分で書いてたり)が多数あり、
書き直しばかりで、なかなか新しい機能を作れないです(笑)

Leave a Reply