LISP:バッククォート記法
マクロを書く際によく使用されることになる、バッククォートの使い方。本やサイトで読むだけではよくわからない部分もあるので試してみた。
まず、そもそも「どういう時にマクロを使用するのか」という点。自分流に表現すると、「ソースコード上の冗長化を解消する」ために使う、
と理解している。たとえば、Javaでインスタンス変数とそのアクセサメソッドしか持たないクラスを書くと以下のようになる。
public class ValueObject { private String s1; private String s2; private String s3; ... private String s9999; getS1(){ return s1; } getS2(){ return s2; } getS3(){ return s3; } ... getS9999(){ return s9999; } setS1(String s1){ this.s1 = s1; } setS2(String s2){ this.s2 = s2; } setS3(String s3){ this.s3 = s3; } ... setS9999(String s9999){ this.s9999 = s9999; } }
上記例の...は省略。9999個のインスタンスフィールドとそのアクセサメソッドのみを持つクラス。
このクラスは、ほとんどコピー&ペーストできるが、実際に書くとなると大変だ。あまりの単調さに
プログラミングやJavaを嫌いになる人もいるかもしれないくらい・・・いや、あくまで個人的にそう思うだけですが。
マクロは、まさにこうした問題を解決するものだと思う。JavaにLispのマクロを適用できるとすると、
このコードは以下のようになりそう。
public class ValueObject { for( int i = 1 ; i < 10000 ; i++ ){ define(i); } } defmacro define (i){ private String s + i; getS + i (){ return s + i; } setS + i (String s + i){ this.s + i = s + i; } }
もちろん、Javaにマクロは(今のところ)ないから、こんな風に書いてもコンパイルエラーになる。
リフレクションを使えば、同じようなことはできるかもしれないが、最初のソースコードのように
誰が見てもどんなクラスかすぐにわかるようにはならないだろう。
あくまで、Lispのマクロが何をやるものなのか、という自分なりの理解を説明するための例で、
実際に上記のようなJavaクラスを書かなければならないようなことはないと思うけれど。
別の言い方をすると、「コードを書くプログラム」とも言える。それができるのは、Lispだと
関数もデータも同じ括弧で表現できるから、という背景もある。実際にはこう書く。
(defmacro when-sample (condition &rest body) `(if ,condition (prog ,@body)))
ここで、`(バッククォート)と,(カンマ)が出てくる。これが非常に重要。
まず、バッククォートはこれが付けられた直後の括弧をシンボルとして扱うようにするもの。
つまり、この中に(+ 1 1)とかがあると、これは(+ 1 1)ではなく、これが評価された結果の2に
なってしまう。マクロはコードを書くためのものなので、書かれるコードに(+ 1 1)という形で
式が残ってくれることを期待しているのに、これが2になってしまうと期待しているコードを生成できない。
書かれた括弧をそのまま残しておきたい、というような時に付ける。
その一方で、バッククォートの括弧の中の一部だけはマクロで処理させたい、というものもある。
そういう場合に使用するのがカンマだ。バッククォートされた括弧内でカンマが使用されると、
そこは例外的にマクロで処理される。上記例からカンマをなくすと以下のように展開される。
(IF CONDITION (PROGN @BODY))
このマクロを実行したときに、CONDITIONという名前の変数が定義されていなければ、
未定義変数が使用された、としてエラー終了する。@BODYも同じ。@BODYという変数が未定義であれば、
エラー終了とする。
逆に、カンマを使用していると、
(IF condition (progn (...)))
という感じになる。conditionとprognの内容は、実際にマクロの引数が渡される。
以上のことから、マクロを書くときには以下の順番で進めていけばよさそう。
- 実際に展開されて欲しい式を完成させる
- 完成式の冒頭にバッククォートをつける
- 実行時に動的に変更したい箇所のみカンマを付ける
あとは練習あるのみだな。他にもいろいろと気をつけないといけないのだろうけれど。