2012年4月26日木曜日

Scheme第7歩


マクロの話その1。
Schemeのマクロはややこしい。しかも、ややこしい点が複数あるから、余計にややこしい。しかしまあ、ややこしいと繰り返してもややこしいだけなので、1つずつややこしい点を解消していこう。

まずはマクロの定義。
(define-macro マクロ名 プロシージャ)
これだけ見れば、別にグローバルな名前付きプロシージャの定義とほとんど同じように見えるが、問題はその展開。
C言語のマクロは、マクロの定義に従って参照個所を機械的に置換するだけだが、Schemeのマクロはdefine-macroで定義したプロシージャの中身で置換する、…わけではない。プロシージャの実行結果で置換するのだ!

例えば、以下のコードについて考えてみよう。
(define foo ‘bar)
(define bar “bar is bar”)
(define-macro foo-macro1 (lambda() ‘foo))
(define-macro foo-macro2 (lambda() foo))
(print “foo-macro1 = ” (foo-macro1))
(print “foo-macro2 = ” (foo-macro2))
foo-macro1のプロシージャの実行結果はfooというシンボル。foo-macro2のプロシージャの実行結果はfooの評価値、すなわちbarというシンボルである。これをふまえて手動でマクロ展開すると
(print “foo-macro1 = “ foo)
(print “foo-macro2 = “ bar)
となる。
…と、ここまではあくまで前処理で、この展開結果を実行するのが本番。したがって、最終的には
foo-macro1 = bar
foo-macro2 = bar is bar
と、fooの評価値とbarの評価値が表示される。

次に引数付きマクロの例。
(define-macro p (lambda (x) (list ‘print x)))
(let loop ((i 1))
  (when (<= i 10)
    (p i)
    (loop (+ i 1))
  )
)
マクロが展開されるとき、マクロの仮引数の値は(quote 実引数)である模様。なので、上記の例を手動で展開すると
(let loop ((i 1))
  (when (<= i 10)
    (print i)
    (loop (+ i 1))
  )
)
となり、めでたく1〜10が表示される。ここでもし、マクロ定義のクォート等を怠るとどうなるか。
(define-macro p2 (lambda (x) (print x)))
(let loop ((i 1))
  (when (<= i 10)
    (p2 i)
    (loop (+ i 1))
  )
)
このマクロp2は(print ‘i)の実行結果、すなわち#<undef>に展開されるので、実行しても10回#<undef>が評価されるだけ。こうならないためにも、マクロは基本的に実行結果がS式になるように定義するのだ。

…とはいえ、listやquoteを使って巧い具合にS式になるようにマクロを定義するのは、結構面倒。そこで出てくるのがquasiquoteなのだが、長くなってきたので次回に続く。

0 件のコメント:

コメントを投稿