2012年4月28日土曜日

Scheme第9歩

マクロの話その3。
マクロの中でローカル変数を使いたい場合。例えば2つの変数の値を入れ替えるマクロを、単純にletを使って書くとこうなる。
(define-macro
  swap (lambda (a b)
    `(let ((tmp ,a)) (set! ,a ,b) (set! ,b tmp))
  )
)
これを
(define x 10)
(define y 20)
(swap x y)
(print "x=" x ", y=" y)
このように使用すれば、期待通りxとyが入れ替わってくれるのだが、
(define foo 100)
(define tmp 200)
(swap foo tmp)
(print "foo=" foo ", tmp=" tmp)
このように使用すると、fooとtmpが入れ替わってくれない。何が起こったかは、マクロを手動で展開してみると一目瞭然。
(let ((tmp foo)) (set! foo tmp) (set! tmp foo))
fooはローカル変数tmpに保存した値を書き戻しただけだし、letの外のtmpは参照すらされていない。

要するに、マクロの引数とローカル変数の名前がかぶると、引数が内側から見えなくなる問題が起こる。ならば、引数とは絶対にかぶらない名前をローカル変数に付けてやればよい。そこで出てくるのがgensym。gensymは評価するたびにユニークなシンボルを返すので、これをローカル変数名として使ってやればよい。
(define-macro
  swap2 (lambda (a b)
    (let ((tmp (gensym)))
      `(let ((,tmp ,a)) (set! ,a ,b) (set! ,b ,tmp))
    )
  )
)
ぱっと見、最初のswapと似てはいるが、ローカル変数tmpの役割がまるで違うことに注意。gensym使用版swap2のtmpは、マクロ展開時に使用するローカル変数。そしてtmpが保持するのはaの値ではなく、aの値を保持する変数の名前。tmpはunquoteされているので、マクロの展開結果にtmpは現れない。

0 件のコメント:

コメントを投稿