スコープを分離するという記事の案を読んでいて、これを実装する上手い案を思いついた。 元記事ではそもそも仕様がどうあるべきかもはっきりした意見が定まっていないようだがここでは import を局所的にやるものという考え方を取ることにする。
一応は R6RS ポータブルなつもりで作ったが、後述の通り全ての処理系で想定通り動くわけではない。 私が R6RS の理解を誤っているのか処理系のバグなのか確信が持てない。
実装
(library (local-import)
(export local-import)
(import (rnrs) (for (rnrs eval) expand))
(define-syntax local-import
(lambda(ctx)
(syntax-case ctx ()
((_ (import-spec ...) body body* ...)
(let ((local-context
(eval '#'foo
(apply environment
`((only (rnrs syntax-case) syntax)
,@(syntax->datum #'(import-spec ...)))))))
#`(begin
#,@(datum->syntax local-context
(syntax->datum #'(body body* ...)))))))))
)
使用例
以下のように local-import の最初の節で import-spec と同じ書き方で必要なライブラリを指示し、その次の節の中では指示されたライブラリを使える。
#!r6rs
(import (only (rnrs) display) ;; 標準ライブラリから import するのは display だけ
(local-import))
(display
(local-import ((only (rnrs) +)) ;; 標準ライブラリから + を import した状態にする
(+ 1 1)))
逆に local-import の最初の節の指定に含めなかったものはその外側のスコープで使えるものであっても使えない。
#!r6rs
(import (only (rnrs) display)
(local-import))
(local-import ((only (rnrs) +))
(display (+ 1 1))) ;; ここで display は使えないのでエラーになる
解説
肝心なのはマクロ展開時に使っている eval である。 必要な環境はこの eval が作り出している。
eval 環境内で作った構文オブジェクトを外に持ち出し、その構文オブジェクトの文脈情報を datum->syntax で本文に転写することで独立した環境で式が解釈される。
実行時に eval を呼ばないので効率も多少は良いと思う。
テスト
上述の使用例が想定した結果になるかいくつかの R6RS 処理系で試してみた。
| 処理系 | 結果 |
|---|---|
| Guile 3.0.11 | OK |
| Chez 10.4.1 | OK |
| larceny 1.3 | OK |
| IronScheme 1.0.603 | OK |
| scheme-rs 最新コミット (4bfb022) | NG |
| rmosh 0.0.13 | OK |
| Ypsilon 0.9.6 | OK |
| Sagittarius 0.9.15 | OK |
私の手元の実行環境 (Windows 10) で動く主要な R6RS 処理系の内で scheme-rs を除く全てで期待通り動作したので仮に R6RS に適合すると言えなくてもおおよそ実用可能だろう。