構文スコープから分離する仕組みを考えてみた

Scheme に構文スコープから分離するための仕組みがあるといいなと、これまで時々感じてきたので、今回すこし考えみた。

# 朱に交われば赤くなる

大域空間を汚すほどでもないけれど意味的にはまとめておきたい関数というのに時々でくわす。

```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`` で局所化するというアイデアがでてくる。

```Scheme:
(define (proc a)
  (let ((b 37.5))
             :
    (let ([f (lambda (x) (* 3.14 (/ (+ x 7.0) 180.0)))])
      (display (f a)) (newline))
             :
    #f))
```

確かにこれでよいのだけれど、ちょっとした問題がある。

<dl>
<dt>構文スコープ変数が参照できてしまう:
<dd><code>lambda</code> が <code>a</code> や <code>b</code> の構文スコープの内側にあるので、
<ul>
<li><code>(lambda ...)</code> が本来は <code>a</code> や <code>b</code> とは無関係な関数であることを読者に明示できない。
<li><code>(lambda ...)</code> の中で間違えて <code>a</code> や <code>b</code> を書いても、処理系はエラーを出さない。
</ul>
<dt>毎回オブジェクト生成される可能性:
<dd>処理系によっては <code>proc</code> が呼び出される都度 <code>lambda</code> オブジェクトが新たに生成されてしまうかもしれない。
</dl>

本来は独立していたはずの ``lambda`` を ``define proc`` や  ``let b`` のスコープ内に入れたことでこういった弊害が生まれる。スコープという朱に交わったがために ``lambda`` が赤くなった。

# 構文スコープ分離構文

というわけで「ここからここまでは構文スコープから完全に独立していますよ。」と明示できる仕組みを考えてみた。

まずは、R6RS 既存の ``library`` 宣言を拝借する案。本来  ``library`` 宣言はトップレベルでのみ使うべきものだけど、構文だけ転用して宣言でなく式としてつかうもの。

* 無名ライブラリーとする
* ``export`` しない
* ``library`` の初期化式 ``(begin ...)`` の評価値が ``library`` 式の値

```Scheme:
(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))
```

ただしこの書き方は

* 無駄に長い &mdash; 無名 ``()`` だとか、必ず空っぽの ``(export)`` になるとか。
* 継承したい &mdash; ``import`` ライブラリーぐらいは親環境から継承してもよいのでは？
* トップレベルの ``library`` 宣言と区別しにくくて処理系泣かせかも。

というわけで、``library`` 構文に代わる新たな構文の導入でもよいかもしれない。

```Scheme:
(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`` というのを定義することもできる。

```Scheme:
(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`` を

```scheme:
(sandbox (依存するライブラリー#1 依存するライブラリー#2 ...)
  ...)
```

という形であるとして、親環境から継承する場合はキーワード ``inherit`` を ``依存するライブラリー#1`` にするのでもよいかもしれない。

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