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
がコード中に複数あったらその順番は?...