Schemeの変数はレキシカルスコープをもちます。すなわち、変数は プログラムテキストのある特定の連続した広がり内のフォームにだけ 見えています。これまで見てきたグローバル変数も例外ではありません。 グローバル変数のスコープはプログラム全体で、たしかに連続していますね。
すでにいくつかローカル変数の例も見ています。それは、
lambda
パラメータで、この手続きが呼出されるたびに束縛され、
そのスコープはこの手続きの本体です。たとえば、
(define x 9) (define add2 (lambda (x) (+ x 2))) x => 9 (add2 3) => 5 (add2 x) => 11 x => 9
ここでは、グローバル変数x
とローカル変数x
がありますが、
後者は手続きadd2
でもたらされたものです。グローバルの
x
は常に9
です。ローカルのx
は最初のadd2
の呼出しでは
3
に束縛されます。2回目の呼び出しではグローバルのx
の値、
すなわち、9
に束縛されます。この手続きの呼び出しから
返ってくれば、グローバルのx
はあいかわらず9
です。
フォームset!
は変数のレキシカル束縛を変更します。
(set! x 20)
はx
のグローバル束縛を9
から20
へ変更します。
それはset!
から見えているx
の束縛だからです。
もし、set!
がadd2
の本体内にあればローカルなx
を
変更したでしょう。
(define add2 (lambda (x) (set! x (+ x 2)) x))
ここのset!
はローカル変数x
に2を加えます。この手続きはローカルの
x
の新しい値を返します(効果という意味では、この手続きは前のadd
と
なんら区別はつきません)。まえと同じようにグローバルのx
について
add2
を呼んでみましょう。
(add2 x) => 22
(グローバルのx
は今は9
ではなく、20
であることを思い
出してください。)
add2
の内側にあるset!
はadd2
が使うローカル変数のx
に影響を
与えるだけです。ローカル変数はグローバルx
の値に束縛されますが、
後者はローカルのx
にset!
によってひきよせられるような
ことはありません。
x => 20
ここでの議論のすべては、ローカル変数とグローバル変数に
同じ識別子をつかっているからこその議論です。どのような
テキストであっても、識別子名x
は字面上もっも近い
変数名を参照します。このことは、外側のすべてのグローバルなx
を
覆い隠すということです。つまり、add2
の中ではパラメータx
は
グローバルなx
を覆い隠しています。
手続きの本体は、それを囲むスコープにある、パラメータが 覆い隠していない変数にアクセスしたり変更したりすることができます。 そうすると次のような興味深いプログラムをつくることができます。
(define counter 0) (define bump-counter (lambda () (set! counter (+ counter 1)) counter))
手続きbump-counter
は引数なしの手続き(サンクとも言う)です。
これは、ローカル変数をもちませんので、なにも覆い隠すものはありません。
呼ばれるたびにグローバル変数counter
を変更し、1ずつカウント
アップし、counter
の現在の値を返します。以下は、連続で
bump-counter
を呼んだ例です。
(bump-counter) => 1 (bump-counter) => 2 (bump-counter) => 3
let
とlet*
ローカル変数は手続きを明示的に生成しなくても導入することができます。
スペシャルフォームlet
はその本体で利用するローカル変数のリストを
導入します。
(let ((x 1) (y 2) (z 3)) (list x y z)) => (1 2 3)
lambda
のときと同様、let
本体内で、ローカルな
x
(1
に束縛されている)はグローバルなx
(これは20
に束縛
されています)を覆い隠します。
ローカル変数の初期化(x
は1
に、y
は2
に、
z
は3
に束縛)はlet
の本体とは考えません。それゆえ、
初期化部でのx
への参照は、グローバルに対する参照であって
ローカルのx
への参照ではありません。
(let ((x 1) (y x)) (+ x y)) => 21
x
は1
に束縛され、y
はグローバルのx
すなわち20
に束縛されますので、このようになります。
let
のレキシカル変数のリストが直列に導入されるほうが
便利なときがあります。つまり、あとからの変数の初期化は
前にでてきた変数のレキシカルスコープ内でおこる
ということです。フォームlet*
はこれを行います。
(let* ((x 1) (y x)) (+ x y)) => 2
y
の初期化中の、x
は直上のx
を参照しています。
この例はlet
が入れ子になった以下のプログラムと完全に同等
(実際以下の場合の便利な省略形として便利)です。
(let ((x 1)) (let ((y x)) (+ x y))) => 2
レキシカル変数が束縛された値は手続きであってもかまいません。
(let ((cons (lambda (x y) (+ x y)))) (cons 1 2)) => 3
このlet
本体中では、レキシカル変数cons
は引数を足します。
外側では、cons
はあいかわらずドット対を作ります。
fluid-let
レキシカル変数は、隠蔽されることがなければ、そのスコープの始めから
終りまで見えています。ときには、レキシカル変数を臨時にある値にするのが
便利なことがあります。これを行うにはfluid-let
フォームを使います。
1
(fluid-let ((counter 99)) (display (bump-counter)) (newline) (display (bump-counter)) (newline) (display (bump-counter)) (newline))
これはlet
と同じように見えますが、グローバル変数counter
を
隠蔽せずに、fluid-let
本体へつづける前に、それを臨時に99
に設定します。したがって、本体部のdisplay
は以下のような
出力をします。
100 101 102
fluid-let
式が評価された後は、グローバルのcounter
は、
fluid-let
の前の値に復帰します。
counter => 3
fluid-let
はlet
とは全く違う効果があることに注意してください。
fluid-let
は、let
がやるように、新しいレキシカル変数を導入する、という
ことはありません。既存のレキシカル変数の束縛を変更し、fluid-let
が済んだとたんにその変更はなかったことになります。
これを理解するために、次のプログラムを考えてみましょう。
(let ((counter 99)) (display (bump-counter)) (newline) (display (bump-counter)) (newline) (display (bump-counter)) (newline))
これはひとつ前の例のfluid-let
をlet
で置き換えたものです。
出力はこうなります。
4 5 6
すなわち、グローバルのcounter
は最初3
でbump-counter
が呼出されるごとに更新されます。新しいレキシカル変数counter
は
初期化部で99
になっていますがbump-counter
の呼び出しにはなんら
影響力をもちません。bump-counter
への呼び出しはローカルのcounter
のスコープ内にありますが、bump-counter
の本体はそのスコープ内にはない
からです。bump-counter
本体部はグローバルのcounter
を参照
しており、その最終の値は6
です。
counter => 6