0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-05-09

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

朱に交われば赤くなる

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

(define (f x)
  (* 3.14 (/ (+ x 7.0) 180.0)))

(define (proc a)
  (let ((b 37.5))
             :
    (display (f a)) (newline)
             :
    #f))

fproc からしか参照されない関数で、しかも極めて一部でしか使われないので、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))

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

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

本来は独立していたはずの lambdadefine proclet 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 がコード中に複数あったらその順番は?...

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?