読者です 読者をやめる 読者になる 読者になる

Septeni Engineer's Blog

セプテーニエンジニアが綴る技術ブログ

Clojureのマクロいじってみた

お知らせ

こんにちはkouです。

lisp系言語といえば大量の()と強力なマクロという印象がありますよね?
個人で適当にClojureいじってるとマクロを利用する事はよくありますが、自分でマクロを作るという事をしてきませんでした。

難しそうってのもありますが、なんか怖そう(マクロの枕言葉のように、基本関数ですむならマクロは使わない方が良いって脅し文句がありますし)ってのもありました。が、
せっかくlisp系言語触ってるのならいじらなきゃもったいないのかなぁとも思い、少しいじってみました。

大体使われるパターン自体は決まっているらしくプログラミングClojure 第2版には、def周りをいじりたいとき、制御構文周りをいじりたいとき、などいくつか使うパターンが記載されていました。

上記の本でClojure本体に入ってるdeclareというマクロを元に色々解説があったのでそれに乗っかっていじってみようと思います。
declare自体は渡されたものでdefするというマクロです。


f:id:taketor:20150722111903p:plain

declareをつかってa,b,cを定義し、それがちゃんと定義されてるかを試してみました。a,b,cはちゃんとありますがdはsymbolとして扱えなくてExceptionでちゃってますね。

一番下の行のmacroexpand-1って関数は与えられたマクロの展開式を表示してくれるものです。
doしてdefするコードが展開されています。

lisp系言語ではコードを実行するタイミングの前にマクロを展開するタイミングがあり
(declare a b c)
というマクロを見つけたら
(do (def a) (def b) (def c))
とコードを展開してからコードをコンパイルしてくれるそうです。

declareの実装は以下のようになってます

(defmacro declare
  [& names] `(do ~@(map #(list 'def (vary-meta % assoc :declared true)) names)))

プログラミングClojure 第2版のマクロのところには以下のようなコードになってます

(defmacro declare
  [& names] `(do ~@(map #(list 'def %) names)))

メタデータを扱うかどうかの差異がありますが今回は下のが簡単なのでそっちを見ていきましょう。

可変長引数で定義するnamesを受け取ってmapでおのおのをdefするようにしてますね。

肝となるのは`と~@で`がついている以降のリストをデータのように扱い、そのデータの中で式の様に評価したい箇所を~@で指定しています。
基本的にリストをデータの様に扱う場合は’を使うのですが、その中で一部だけ評価したい場合に`を使うようです。

lisp系言語はこんな感じでリストをデータとして扱うか式として扱うかを使う側が制御できます。

ここで出てきた`,~@,’とかはリーダーマクロと呼ばれるもので、これらも特定の式に変換されるとの事です。
これをつかってvector、map、setなどのコレクションを作ってるようで、ここはなんかなるほどぉっとなりました。

とりあえずdeclareを少しいじってみましょう。declareには束縛された値がないのが寂しい感じなので値を束縛できるようにしてみようかと思います。

(defmacro [& names]
  `(do ~@(map #(list 'def % (str %)) names)))


単純にシンボルを文字にしたもので束縛してみました。

f:id:taketor:20150722112123p:plain

ちゃんとシンボルに値が束縛されてます、これくらいなら簡単ですね。

お次は変数を一気に定義するマクロを作ってみます。
定義したいシンボルの名前の後に数値を渡して、その数分一気にvarを定義するもの。
hoge 20と渡すとhoge0〜hoge19までの定義が一気にされる感じです。

(defmacro declarenum [name number]
  `(do ~@(map #(list 'def (symbol (str name %)) %) (range 0 number))))

f:id:taketor:20150722112141p:plain


mapでぶん回すのを引数のnumberからrangeで生成した数値に変更し、ついでぶん回した数値をシンボルに束縛してみました。

こんな記述で一気に定義できるようになりました。
なんか色々な事ができそうな感じで夢が膨らむような気もしますが、やっぱり使いどころがよくわからない感じもします。

コンパイルされるソースコードをマクロ展開時のプログラミングの結果として記述していく所や、そこにあるソースコードをデータとして扱うかプログラムとして扱うかとか、この辺りがlispの悟り云々辺りなのでしょうけど私が悟るのはまだまだ先になりそうです。