Scheme に構文スコープから分離するための仕組みがあるといいなと、これまで時々感じてきたので、今回すこし考えみた。
朱に交われば赤くなる
大域空間を汚すほどでもないけれど意味的にはまとめておきたい関数というのに時々でくわす。
(define (f x)
(* 3.14 (/ (+ x 7.0) 180.0)))
(define (proc a)
(let ((b 37.5))
:
(display (f a)) (newline)
:
#f))
f は proc からしか参照されない関数で、しかも極めて一部でしか使われないので、proc 内の let で局所化するというアイデアがでてくる。
(define (proc a)
(let ((b 37.5))
:
(let ([f (lambda (x) (* 3.14 (/ (+ x 7.0) 180.0)))])
(display (f a)) (newline))
:
#f))
確かにこれでよいのだけれど、ちょっとした問題がある。
- 構文スコープ変数が参照できてしまう:
-
lambdaがaやbの構文スコープの内側にあるので、-
(lambda ...)が本来はaやbとは無関係な関数であることを読者に明示できない。 -
(lambda ...)の中で間違えてaやbを書いても、処理系はエラーを出さない。
-
- 毎回オブジェクト生成される可能性:
- 処理系によっては
procが呼び出される都度lambdaオブジェクトが新たに生成されてしまうかもしれない。
本来は独立していたはずの lambda を define proc や let b のスコープ内に入れたことでこういった弊害が生まれる。スコープという朱に交わったがために lambda が赤くなった。
構文スコープ分離構文
というわけで「ここからここまでは構文スコープから完全に独立していますよ。」と明示できる仕組みを考えてみた。
まずは、R6RS 既存の library 宣言を拝借する案。本来 library 宣言はトップレベルでのみ使うべきものだけど、構文だけ転用して宣言でなく式としてつかうもの。
- 無名ライブラリーとする
-
exportしない -
libraryの初期化式(begin ...)の評価値がlibrary式の値
(define (proc a)
(let ((b 37.5))
:
(let
([f
(library ()
(export)
(import (rnrs base))
(begin
(lambda (x) (* 3.14 (/ (+ x 7.0) 180.0))))
)
])
(display (f a)) (newline))
:
#f))
ただしこの書き方は
- 無駄に長い — 無名
()だとか、必ず空っぽの(export)になるとか。 - 継承したい —
importライブラリーぐらいは親環境から継承してもよいのでは? - トップレベルの
library宣言と区別しにくくて処理系泣かせかも。
というわけで、library 構文に代わる新たな構文の導入でもよいかもしれない。
(define (proc a)
(let ((b 37.5))
:
(let
([f
(sandbox
(lambda (x) (* 3.14 (/ (+ x 7.0) 180.0))))
])
(display (f a)) (newline))
:
#f))
これならシンプルでよいかも。
sandbox は依存するライブラリーは親環境から継承しつつも、構文スコープからは独立しているし、このコードが書かれているトップレベルからも独立しているとする。
ただし sandbox 内は独自の名前空間を持っているとする。なので PI というのを定義することもできる。
(define (proc a)
(let ((b 37.5))
:
(let
([f
(sandbox
(define PI (acos -1))
(lambda (x) (* PI (/ (+ x 7.0) 180.0))))
])
(display (f a)) (newline))
:
#f))
…というのはどうだろう?
または sandbox を
(sandbox (依存するライブラリー#1 依存するライブラリー#2 ...)
...)
という形であるとして、親環境から継承する場合はキーワード inherit を 依存するライブラリー#1 にするのでもよいかもしれない。
(define (proc a)
(let ((b 37.5))
:
(let
([f
(sandbox (inherit)
(define PI (acos -1))
(lambda (x) (* PI (/ (+ x 7.0) 180.0))))
])
(display (f a)) (newline))
:
#f))
初期化はいつ?
問題がとりあえずひとつ。sandbox の初期化はい行われるのか、というか、どの順番で行われるのかが、はっきりしない。sandbox が書かれているコードのトップレベルよりも前に、sanbox の評価が行われるとすればとりあえずよさそうに思えるけど、本当にそんなあいまいな感じでいいのかしら?sandbox の中に sandbox があったらどうなる?sandbox がコード中に複数あったらその順番は?...