ようやく第7章「マクロ」にたどり着いたのですが、のっけから恐ろしいことが書いてありました。
「マクロの概念は標準のSchemeには含まれていないが……」
え?……え!?
マクロがLispのウリじゃなかったんですか?
でもマクロの章があるってことは、マクロはあるんですよね? あるんですよね?
ドギマギしながら「7.1 マクロ」を読みましたが、まぁこれが、何の話なのか分からない分からない。「マクロ・フォーム」「マクロ展開関数」「マクロ展開」……で、どれがマクロなの? 状態ですよ。
とりあえず具体例が無いと具体的にコードを打ってみないと、何がなんだかわけが分からないよ! な、いたいけな女子中学生を誑かして魔法少女にしてしまう腹黒マスコットみたいなことを口走ってるしかないので、「7.2 マクロの定義」に進んでから考えることにしました。
が、コードを見てみて解説を読んでみても今ひとつ納得がいきません。「ホントにこれでこうなんのかなぁ」と疑問に思いましたので、EmacsでGauche起動、書いてある通りのコードを打ち込んでCx-Ce!
gosh> *** ERROR: unbound variable: macro
Stack Trace:
「Gauche macro」で検索しますと「Gaucheユーザリファレンス 5. マクロ」が見つかりましたよ。どうやらGaucheには「健全なマクロ」と「伝統的なマクロ」の2種類のマクロがあるようです。さてさて「健全なマクロ」の方は……ちょっと置いておこう。何か私の読んでいるマクロと違う。
「伝統的なマクロ」を見てみますと、どうやらこちらの方で行けそうです。"macro" を "define-macro" に置き換えればいいようですね。実際、 "define-macro" と打つとEmacsが文字に色を付けてくれました。
ところで when 式は Gauche にすでにあるそうなので、"incf" の方をやってみました。
(incf x)
とすると x が1ずつ増加し、
(incf x y)
とすると x が y 分、増加するマクロです。
(define-macro incf
(lambda (form)
(list 'set! (cadr x)
(list '+ (cadr x)
(if (null? (cddr x))
1
(caddr x)
)
)
)
)
)
これで定義して
(define x 0)
(incf x)
すると……
gosh> *** ERROR: Compile Error: pair required, but got x
"(stdin)":90:(incf x)
DA.YO.NE (EAST END×YURI 1994年)。そうですよね。そうなる気がしてた。だってlambda式に渡される "incf" の引数は x (アトム)なのに car/cdr したら
*** ERROR: (中略)pair required
くらいますよね……これ、ホンマにどないなっとるんや? ホンマにこれで行けるんか?
どうにも理解できなかったので、自分の理解できそうな範囲で書き直してみました。ただし「困難は分割せよ」ということで "incf" の機能を
1.(incf x) でxが1ずつ増加する
2.(incf x y) ならxはy増加する
のうち、1だけに絞ってやってみます。
(define-macro incf
(lambda (form)
(list 'set! form (list '+ form 1))
)
)
こうやってCx+Ceすると
gosh> #<undef>
incf
gosh> #<macro incf>
で
(define x 0)
gosh> x
x
gosh> 0
としてやってみますと……
(incf x)
gosh> 1
gosh> 2
gosh> 3
……
Cx+Ceのたびにxが1ずつ増加しました。やったね。
しかしここで疑問が湧きました。「これって関数とどう違うの? "dfine-macro"じゃなくて"define"でいいんじゃね?」。やってみるにしくはなし、実際に書きかえてみました。
(define incf+
(lambda (form)
(list 'set! form (list '+ form 1))
)
)
を評価したあと
(incf+ x)
をCx+Ceしてみますと……
gosh> (set! 8 (+ 8 1))
(注:たまたまこのとき変数xに8が入ってた)
……この時の私の感動と悟り感覚を分かってもらえるでしょうか、いや分かるまい。何故なら私より進んでいるSchemerなら「は? 当たり前じゃん」と思うでしょうし、私より未熟なSchemer(すなわち何も知らない人)なら何も分からないでしょうからね!
この時ようやく私は「マクロ」というものの概念をおぼろげながら理解したのですよ。某国民的アニメに例えると
野豚(仮名)「え〜ん、デュラえも〜ん(仮名)。斯く斯く然々を斯く斯く然々で斯く斯く然々にする道具をだしてよ〜」
デュラえもん(仮名)「もう、野豚(仮名)くんはしょうがないなぁ。(ちゃらららっちゃら〜♪)ほにゃらららららー!(←道具の名前)」
これだ! マクロってきっとこれだ! こっちが「斯く斯く然々な仕事をする斯く斯く然々な処理を作ってよ〜」と書くと、その斯く斯く然々をリストとして組み立てて、さらにそのリストを実行してくれるデュラえもん(仮名)のような存在、それがマクロなのだ!……多分。
(ただし
(set! 8 (+ 8 1))
を評価してもエラーになります。xに8が入っていたからこうなったわけで、
(set! x (+ x 1))
が組み立てられて、そしてxに8が入っているので前述のような結果になったのだと思われます)
ははぁ、なるほど。よく聞く「Lispはすべてがリスト(と、アトム)」と言うのが、何となく腑に落ちたましたよ。この理解が正しいのかどうか分かりませんが。
それでは条件1、2を揃えた incf 関数、Gauche版が以下です。
(define-macro incf
(lambda (form . x)
(list 'set!
form
(list '+
form
(if (null? x)
1
(car x)
)
)
)
)
)
これで
(incf x 10)
とやるとxが10増加されます。やったね。キモは9行目の
(car x)
ですね。以前にも記しましたように、残余パラメータ(可変長引数)はリスト化(括弧に包まれる)ので、car でリスト(括弧)を剥がしております。一度ドツボにハマった今の俺に隙は無い。(←またハマるフラグ)
こうしてGaucheで incf マクロが無事作成できますと、むしろ「Scheme入門」にある例の方が不思議に思えてくるわけですよ。……ひょっとしてこの書の方が間違ってるんじゃね?(うまく行かないとまず他人のせいにする人間の思考)
「Scheme入門」に書いてある incf マクロの定義は以下です。
(macro incf
(lambda (form)
(list 'set!
(cadr form)
(list '+
(cadr form)
(if (null? (cddr form))
1
(caddr form)
)
)
)
)
)
(インデント/改行は私が変えております)
いやいやいやいや。おかしいよなぁ。これで
(incf x 10)
とかやっても引数は
x
と
10
と、アトムなので car/cdr できるわけないよなぁ。本当にこれでなるのかな? と前回記しましたようにTUTSchemeをインストールして実行してみますと……あ、TUTSchemeではちゃんとなる。何でだ?
じゃあということで Gauche でやってみたように macro を define に置き換えてみましたが
SC>
Error: Illegal argument.
Argument must be a pair.
In (cadr 1).
とエラーになるので、うーむ、うーむ、と考えた挙句、
(list 'set!
(cadr form)
(list '+
(cadr form)
(if (null? (cddr form))
1
(caddr form)
)
)
)
だけ取り出して評価しましたが、あたりきしゃりき、「formって変数はバンドされてねえよ」とエラーが出ましたので、
(incf x 10)
の引数、xと10をリストにして
(define form '(x 10))
として評価しますと
(set! 10 (+ 10 1))
いや、10増えるはずで、10が1増えるわけでは……あ!(閃いた)
(define form '(incf x 10))
としてもう一度
(list 'set!
(cadr form)
(list '+
(cadr form)
(if (null? (cddr form))
1
(caddr form)
)
)
)
を評価しますと
(set! x (+ x 10))
な……なるほど! 謎は全て解けた。
つまりTUTSchemeでは macro でマクロを定義するとき、lambda に渡される引数は、マクロ名を含むリスト全て、なのですね。
まとめ
・Gauche では、"define-macro" (伝統的なマクロ)を定義する際、lambda式に渡される引数は、マクロの引数
・TUTScheme では、"macro" を定義する際、lambda 式に渡される引数は、マクロ名を含めたリスト
ふー。ちょっと引っ掛かったなー。
まあ、こうした試行錯誤を経て、人は大人になってゆくんですね。(声:内山昂輝)