R6RS:翻訳:Standard Libraries:12.6 Syntax-object and datum conversions
構文オブジェクトとデータの変換
[procedure] (syntax->datum syntax-object)
構文オブジェクトから構文情報をすべて取り去り、対応する Scheme の「データ」を返す。
このとき、識別子はその記号的な名前になり、eq? で比較できるようになる。 したがって、symbolic-identifier=? という手続きは次のように定義できる。
(define symbolic-identifier=?
(lambda (x y)
(eq? (syntax->datum x)
(syntax->datum y))))
[procedure] (datum->syntax template-id datum)
template-id はテンプレート識別子であり、datum はデータ値でなければならない。この手続きは template-id と同一の文脈情報をもつ、 datum の構文オブジェクト表現を返す。 このとき、この構文オブジェクトは template-id が挿入されたのと同時にコードに挿入されたかのようにあつかわれる。
datum->syntax をつかうと、その識別子がもともと入力にあったかのようにあつかわれる暗黙の識別子をつくり、静的スコープの規則を「曲げる」ことができるようになる。 すなわち、入力フォームに明示的にあらわれなかった識別子に対して参照可能な束縛や参照を挿入する構文抽象を定義することができるのである。 たとえば、下に示す loop 式の定義は、この統制された識別子捕捉をつかってループの本体からの脱出手続きを変数 break に束縛している(派生形式 with-syntax は let のようにしてパターン変数を束縛する。R6RS:翻訳:Standard Libraries:12.8 Derived forms and procedures 節参照)。
(define-syntax loop
(lambda (x)
(syntax-case x ()
[(k e ...)
(with-syntax
([break (datum->syntax #’k ’break)])
#’(call-with-current-continuation
(lambda (break)
(let f () e ... (f)))))])))
(let ((n 3) (ls ’()))
(loop
(if (= n 0) (break ls))
(set! ls (cons ’a ls))
(set! n (- n 1))))
⇒ (a a a)
loop を次のように定義すると、
(define-syntax loop
(lambda (x)
(syntax-case x ()
[(_ e ...)
#’(call-with-current-continuation
(lambda (break)
(let f () e ... (f))))])))
変数 break は e ... 中で参照できない。
下の include の定義に示すとおり、引き数 datum は任意の Scheme フォームであってよい。
(define-syntax include
(lambda (x)
(define read-file
(lambda (fn k)
(let ([p (open-file-input-port fn)])
(let f ([x (get-datum p)])
(if (eof-object? x)
(begin (close-port p) ’())
(cons (datum->syntax k x)
(f (get-datum p))))))))
(syntax-case x ()
[(k filename)
(let ([fn (syntax->datum #’filename)])
(with-syntax ([(exp ...)
(read-file fn #’k)])
#’(begin exp ...)))])))
(include "filename") は、"filename" というファイル中のフォームを下位要素にもつ begin 式に展開される。 たとえば、(define f (lambda (x) (g (* x x)))) という内容のファイル flib.ss と (define g (lambda (x) (+ x x))) という内容の glib.ss というファイルがあるとき、次の式
(let () (include "flib.ss") (include "glib.ss") (f 5))
は 50 に評価される。
include の定義では、datum->syntax をつかってファイルから読み込んだオブジェクトを、適切な字句文脈上の構文オブジェクトに変換している。 そのため、ファイル中の識別子の参照や定義は include フォームの現れた場所のスコープに入る。
datum->syntax を使うと、健全性を完全に破壊し、昔ながらの Lisp スタイルのマクロを書くことさえできるようになる。 下で定義している lisp-transformer 手続きは入力をデータに変換する変換子を作成し、このデータに対してプログラマの定義した手続きを呼び出してその戻り値をもともとのマクロの使用の現れたスコープの構文オブジェクトに戻す。
(define lisp-transformer
(lambda (p)
(lambda (x)
(syntax-case x ()
[(kwd . rest)
(datum->syntax #’kwd
(p (syntax->datum x)))]))))