スコープを曲げる

何度か取り上げたような気がするが、 Scheme のマクロ機構における datum->syntax の第一引数の意味が未だによくわからない。 あらためて規格を読んでみることにした。

(datum->syntax template-id datum)

template-id はテンプレート識別子であり、datum はデータ値でなければならない。 この手続きは template-id と同一の文脈情報をもつ datum の構文オブジェクト表現を返す。 このとき、この構文オブジェクトは template-id が挿入されたのと同時にコードに挿入されたかのようにあつかわれる。

ここで「文脈情報 (contextual information)」という言葉がよくわからない。
更に続きの文を見ると…

datum->syntax をつかうと、その識別子がもともと入力にあったかのようにあつかわれる暗黙の識別子をつくり、静的スコープの規則を「曲げる」ことができるようになる。 すなわち、入力フォームに明示的にあらわれなかった識別子に対して参照可能な束縛や参照を挿入する構文抽象を定義することができるのである。

とあることから、私はおぼろげに文脈情報というのはスコープかそれに密接に関係する何かであろうと理解しているのだが、スコープの規則を曲げるという感覚がイマイチつかめていない。
R6RS にある例はかなり単純なものでしかなく、意味するところがどうにも読み取れない。
そこで、いかにもスコープを曲げたという感じのするマクロを実際に書いてみることにした。 動作確認は Ypsilon と Mosh と Petite Chez Scheme で行っている。 (にちゃんねるの「Lisp Scheme Part27」スレッドに先日書いたものを少し変更したものである。)

(define-syntax let/scope
  (lambda(x)
    (syntax-case x ()
      ((k scope-name body ...)
       #'(let-syntax
             ((scope-name
               (lambda(x)
                 (syntax-case x ()
                   ((_ b (... ...))
                    #`(begin
                        #,@(datum->syntax #'k
                             (syntax->datum #'(b (... ...))))))))))
           body ...)))))

使い方は以下のような感じだ。

(let ((x 1))
  (let/scope d1
    (let ((x 2))
      (let/scope d2
        (let ((x 3))
          (list (d1 x) (d2 x) x))))))  ;; ⇒ (1 2 3)

通常はシャドウイングされてしまう変数 x をスコープの名前を指定することでそれぞれのスコープにおいての変数 x として解決する。
にちゃんねるでコメントもらった情報によると「ビューティフルコード」という書籍で Scheme のマクロを取り上げているらしい。 その他の部分も評判は良いらしいので、そのうち購入するつもりだ。

ビューティフルコード (Theory in practice)

ビューティフルコード (Theory in practice)


Document ID: c1841d32bcd7a897a3d1b4073b2a67b4