once-only

CommonLisp の世界では once-only というマクロがよく使われるそうだ。 defmacro で普通にマクロ定義した場合、引数を複数箇所で展開すると評価も複数回行なわれてしまうのだが、 once-only はそれを解決するものだとのこと。

例えば、こんなマクロ定義を考える。

(defmacro square (x)
  `(* ,x ,x))

この定義に基いて

(square (+ 1 2))

を展開すると

(* (+ 1 2) (+ 1 2))

になる。
ここでは (+ 1 2) が二度評価されているに過ぎないが、コストの大きい式だったり、副作用のある式だったりした場合には都合が悪いこともある。 そんなときに once-only の出番だ。
先程の定義にちょっと追加してこんな風にすると一度しか評価されないように出来る。

(defmacro square (x)
  (once-only (x)
    `(* ,x ,x)))

先日は CommonLisp の defmacro 相当を Scheme (R6RS) で定義した。

http://saito.hatenablog.jp/entry/20100420/1271778879

その defmacro を用いて、また、あわせて使えるように once-only を定義してみよう。

(library (once-only)
  (export once-only)
  (import (rnrs) (defmacro))
	 
  (defmacro once-only (vars . body)
    (let ((gs (map (lambda(_) (gensym)) vars)))
      `(let ,(map (lambda(g) `(,g (gensym))) gs)
         `(let (,,@(map (lambda(g n) ``(,,g ,,n)) gs vars))
            ,(let (,@(map (lambda(g n)`(,n ,g))  gs vars))
               ,@body)))))
)

CommonLisp では loop マクロを使って定義するのが定番らしいが、それを map で代用した。

Document ID: a546fb98d6e1ea05aa2132af35a808f4