Schemeにおける変数のスコープ

適当に実装していたら、間違えているらしい。ちょっとR5RSを読み直してみる。

3.1 変数、シンタックス、キーワード、領域

AlgolとPascalに同じく、しかしCommon Lispを除くその他のほとんどのLisp方言とは異なり、Schemeはブロック構造化された静的スコープを持っている。プログラム内で変数がバインドされた記憶域の一つ一つに、プログラムテキストのそのバインディングが見える内部領域の一つが対応する。この領域は、バインディングを作成した特定のバインディング構成手続きによって定まる。例えばバインディングがlambda式で作成されたとすれば、領域はそのラムダ式全体である。識別子を呼び出すとそのたびに、その変数が使用された領域のもっとも内側を構成する、変数バインディングへの参照が行なわれる。

lamda式中で、宣言した変数はlamda式中でのみ有効ですよ、って意味かな?

(set! f (lambda () (define x 10))) => f
(f) => #<undef>
x   => #<undef>

4.1.6 代入式

構文 (set! <変数> <式>)<式>が評価されて、<変数>がバインドされている記憶位置に結果の値が保管される。<変数>は、set!式を取り囲む領域内かトップレベルの、いずれかにバインドしなければならない。set!式の結果は不定である。

  (define x 2)
  (+ x 1)     => 3
  (set! x 4)  => unspecified
  (+ x 1)     => 5

あらかじめdefineされた変数にしか代入できない、って意味ね。ここ、間違えてたわ。

; OK
(define x 10)
(set! x 20)

; ERROR
(set! y 100)

4.2.2 バインド構造

実装してないので、パス。

5.2 定義

定義は、必ずというわけではないが、式が許される一定の文脈で有効である。定義は、<プログラム>のトップレベルと<ボディ>の開始点でのみ有効である。

定義は以下の形式の一つで行なわなければならない。

  • (define <変数> <式>)
  • (define (<変数> <仮引数群>) <ボディ>)

<仮引数群>はゼロ個以上の変数の連続か、もしくは(ラムダ式の場合のように)一つ以上の変数の連続に、空白で区切ったピリオドともう一つの変数を続けたものでなければならない。 この形式は以下に等価である。

(define <変数>
  (lambda (<仮引数群>) <ボディ>))
  • (define (<変数> . <仮引数>) <ボディ>)

<仮引数>はただ一つの変数でなければならない。この形式は以下に等価である(25)。

(define <変数>
  (lambda <仮引数> <ボディ>))

トップレベル、もしくはスコープの先頭とかC言語っぽいなぁ。こんな制限つけるのも面倒だから、無視しよう。

5.2.1 トップレベルの定義

プログラムのトップレベルにおいて、次の定義

(define <変数> <式>)

は本質的に、<変数>がバインド済みの場合の次の割り当て式と同じ効果がある。

(set! <変数> <式>)

ただし<変数>がバインドされていない場合は、割り当てを実行する前に定義を行なうことによって、<変数>が新しい記憶域にバインドされる。バインドされていない変数にset!を実行した場合はエラーになる。

(define add3
  (lambda (x) (+ x 3)))
  (add3 3)                            =>  6
  (define first car)
  (first '(1 2))                      =>  1

処理系によっては、考えられる変数をすべて記憶域にバインドした初期環境を使用するものがある。その記憶域の大部分に含まれる値は未定義である。そのような処理系の場合、トップレベルの定義はまさしく割り当てに等しい。

あらかじめ、すべての変数がnilにバインドされている処理系も許されるんだ。Rubyで実装する場合、こっちのほうが楽だね。

まとめ

  • すべての変数にたいするset!が許されている現在の実装も別に間違っていない
  • defineの仕様が気に入らないからといって、文法を変更するとBNFを自分で考える必要がでてくる
  • defineを使った関数の定義、便利そうだな