2022/10/09

Common Lisp のマクロを学ぶ

基本となる記号

Common Lisp(私が使用しているのはSBCL)では、老眼に優しくないクオートとバッククオートがそれぞれ違う機能として使われています。(これらはリーダーマクロもしくはリードマクロと呼ばれているもので、実はこれ自体を変更するset-macro-characterset-dispatch-macro-charaterなんていう関数も存在しますが、今回は触れません)

マクロを定義する時、このThe ROUGANに優しくないクオートとバッククオートを使います。

まずはその機能を知っておくことが重要です。

クオート'は、毎回quoteと書くのが面倒なので作られたもので、以下のような機能を持つものです。


* '(1 2 3)
(1 2 3)
* (quote (1 2 3))
(1 2 3)
* 'x
X
* (quote x)
X
* '(+ 1 2)
(+ 1 2)
* (quote (+ 1 2))
(+ 1 2)

与えられたものを評価せず、そのまま返す、という機能です。

次にバッククオート`ですが、Lispではquasiquote(クアージークオート)と言います。quasiは「疑似の」「類似の」などといった意味です。したがって、機能的には


* `(1 2 3)
(1 2 3)
* `(+ 1 2)
(+ 1 2)
* `x
X

「同じやないか〜いっ!」と思われた方もいらっしゃるようにほぼ同じです。しかし、ここからが違います。


* `(1 2 ,(+ 1 2))
(1 2 3)

クアージークオートの式ではカンマ,が使えて、そのカンマがあるところだけ、部分的に評価できてしまうんです。これはクオートのほうにはない機能です。さらに@も組み合わせて以下のようなことができます。


* `(1 ,@nil)
(1)
* `(1 ,@(list 2))
(1 2)
* `(1 ,@'(2))
(1 2)

,@がついた式は、それを取り囲んでいるリストと繋ぎ合わせるという機能があります。

独自マクロの定義

記号の意味が分かれば、独自のマクロを定義したくなります。早速、定義してみましょう。

新たに独自のマクロを定義する場合、defmacroを使います。例えば、->なんていう記号を作って、関数fと引数xを渡し、xにfを適用した結果を得るというようなマクロをつくってみようかと思います。


* (defmacro -> (f x)
    `(funcall ,f ,x))
->
* (-> #'print 1)

1
1

うまくいきました。

マクロというのは、簡単に言ってしまうと「置き換え」なので、上記の定義の解釈としては、->が使われている式を発見したら、(funcall f x)に置き換えます、というようなことになります。したがって、(funcall #'print 1)と書くのと同じ意味になります。ちなみに#'functionの省略で、関数の参照という意味があります。

このように、マクロを定義することは実はそれほどややこしいものではありません。

マクロの問題点

Common Lispの強みの1つであるマクロですが、実はこれは結構自己中心的になってしまうのではないか、なんて思います。

個人で開発・メンテナンスする場合は、自分自身が作ったマクロなので、当然理解しているでしょう。しかし、誰かとともに開発する場合、例えば上記で作った->というマクロを私は上記のような機能のマクロを作り、Aさんは->で全然違う機能のマクロを書いているということも十分にありえるわけです。そして、Aさんは自身で作ったマクロと同じ機能だと思って使ってしまうこともあるわけです。

したがって、マクロは便利であるけれど、濫用するとよくない。誰かと開発する場合は、しっかり確認すること、もしくは誰が見ても分かるような工夫が必要。なるべくもともと備わっているもので考えよう。