2012年4月23日月曜日

Scheme第4歩

変数のスコープの話。

まず、これまで使って来た
(define global-variable 100)
はグローバル変数の定義。そして、プロシージャの話のところでさらりと書いたけれど、lambdaの仮引数はその中でのみ有効なローカル変数。

さて、(今のところ)ブロックスコープの無いJavaScriptだと、ブロックの代わりに
js> (function() {...})();
みたいなことをやるけれど、Schemeもlambdaを使わないと新たなスコープを導入できないのか? というと、そんなことはない。
(let
  ((a 1) (b 2) (c 3))
  (display (+ a b)) (newline)
  (display (+ b c)) (newline)
  (display (+ c a)) (newline)
)

このようにletを使うと、最初の引数に従って
a = 1
b = 2
c = 3
という3つのローカル変数が作られた上で、2番目以降の引数のフォームが評価される。
ここで、このローカル変数の初期値を評価するときのスコープはletの外側のものであることに注意。
(let
  ((a 1))
  (let
    ((a 2) (b a))
    (display b) (newline)
  )
)
この内側のletのローカル変数bを初期化するときのaは、あくまで外側のa。従って、これを評価すると2ではなく1と表示される。もし内側のbを初期化するとき、直前に初期化された内側のaの値を使いたければ、let*を使う。
(let*  ((a 1) (b (+ a 1)) (c (+ b 1))) ...)
(let ((a 1))
  (let ((b (+ a 1)))
    (let ((c (+ b 1))) ...)
  )
)
と等価。

さて、ここまではグローバル変数やローカル変数を定義してばかりだったが、変数なので値を変更することも当然できる。その方法は、グローバルかローカルかには依存せず
(set! 変数名 新しい値)
とする。set!はあくまで値の再設定なので、新しい変数の導入にset!は使えない。

ここで、なぜ変数の定義には初期値が必要なのか? setではなくset!なのか? おそらく、あまり変数の再設定はするなという思想を反映しているんじゃないかな。
…とは言え、今でこそ変数の再利用は保守性を下げる悪癖でしかなくなったけれど、コンパイラが賢くない時代においてはメモリ節約のテクニックだった。Schemeはかなり歴史のある言語のようだけど、昔からあえて再設定を醜くしていたのなら、慧眼だなぁ。
後発のHaskellですら、変数を再設定させない仕様は冒険だったと思う。嫌いじゃないけど。

0 件のコメント:

コメントを投稿