チルダクオート
マクロを組むときには変数捕捉に気をつけないといけないが、実は変数捕捉をうまく使う方法もあって、On Lispにそんな例が載っている。
http://www.komaba.utmc.or.jp/~flatline/onlispjhtml/anaphoricMacros.html
一番簡単な例としては、アナフォリックIFがある。
(defmacro aif (test-form then-form &optional else-form) `(let ((it ,test-form)) (if it ,then-form ,else-form))) (aif (big-long-calculation) (foo it))
この例では、比較結果である変数itを捕捉し、aifのthen-form内で使えるようにする。
Clojureでは少し長くなって、
(defmacro aif ([test-form then-form] `(aif ~test-form ~then-form nil)) ([test-form then-form else-form] `(let [~'it ~test-form] (if ~'it ~then-form ~else-form)))) (aif (big-long-calculation) (foo it))
である。*1
アナフォラのitの前にチルダクオート(~')が付いているが、これはClojureのバッククオートがCommon Lispのものとは違う動作をするため。
Common Lispのバッククオートは、アンクオートできるクオートである。Clojureのバッククオートは、これに「名前空間解決&付加」という機能がプラスされている。
素のシンボルが欲しければ、クオートされたシンボルをアンクオート(すなわちチルダクオート(~'))しないといけない。
* `(hoge list ,'list) (HOGE LIST LIST)
user=> `(hoge list ~'list) (user/hoge clojure.core/list list)
この違いは、マクロを使うときの名前衝突への対策のため。ちなみに、各言語での名前衝突への対策はこんな感じ:
- Common Lisp : そもそもLisp-2*2なので変数名と関数名は衝突しないよ派
- #'とかfuncallとかうぜぇ。
- Scheme(R4RS〜) : 名前の衝突を自動回避するならLisp-1*3でも大丈夫だよ派
- アナフォリックマクロみたいな、意図的に安全対策を破る(orサボる)ようなマクロは書けない。
- Gaucheとかはdefine-macroが使えるけど、名前の衝突に関しては自己責任の部分がすごく増える。
- Clojure : バッククオートの定義を変えて、read時に名前空間を付け足してしまえばLisp-1でも大丈夫だよ派
Clojureでは、バッククオートの定義を変えてしまったために、アナフォラを導入するときにチルダクオート(~')が必要になってしまったけど、逆に安全対策を破っていることを示すサインみたいになっていて、かえって良いかも知れない。