3. その結果を表示する
3.9 局所変数
任意の数の引数を手続きに渡すのに、(foo (list a b c d e))として引数をすべて
1
つのリスト入れるのもひとつの方法です。すると手続き fooは、
1
引数の手続きとして作ればよいことになります。ですがいちいちリストにまとめるのも大変です。
手続きが可変数の引数を受けとるには、次のようにします。
(define h手続き名i (lambda h引数名i
h式のならび i ))
ここで h引数名iは記号のリストではなく、ひとつの記号であることに注意して下さい。
たとえば (define foo
(lambda s
(if (null? s) '()
(list-ref s (- (length s) 1)))))
は、fooの最後の引数を返す手続きです。(foo 1 2 3 4)の評価において仮引数sは、値
(1 2 3 4) を持つことになります。
> (foo 1 2) 2
> (foo 1 2 3 4 5) 5
> (foo) ()
(define y 0) (define (f2 x)
(set! y (* x x))
(+ (* y y) (* (g x) y)))
(
最初の行の (define y 0)は単に変数 y を用意しているだけで、y の値を0
にすること自体は重要でなく、値は何でもかまいません。
)
この方法では確かにかけ算の回数は減っ ています。ですが f1 が呼び出している手続きgが 、一時的な値の保持に yを使っている かもしれません。もしそうならば gが yの値を書き換えてしまい、予期しない結果を得る でしょう。ですのでこの方法は完全ではありません。以上の問題は、f2の内部だけでしか使われない変数を用意してやることで解決できます。
このように使える範囲を一部だけに限定した変数を局所変数
(local variable)
といいます。Scheme
では局所変数を用意する特殊形式としてlet,
let*,
letrec が用意されています。ここでは letと let*について説明します。letrecは
6.4
で紹介します。3.9.1 let による局所変数
局所変数を使えば 、関数
f
を計算する手続き fは次のようになります。(define (f x)
(let ((y (* x x)))
(+ (* y y) (* (g x) y)))) letの一般的な形は
} (let ((h変数1i h式1i) (h変数2i h式2i)
...
(h変数
n
i h式n
i))h本体i)
です。なお h本体iは、式のならびです。
letの動作は以下の通りです。まず変数h変数1i
,
h変数2i,...,
h変数n
iの値を保持する場 所を確保し 、h式1i,
h式2i,...,
h式n
iを評価します。そしてそれぞれの値を、 新たに確保さ れたh変数1i,
h変数2i,...,
h変数n
iの値を保持する記憶場所に代入します。これで局所変数の 初期値が決められます。h本体i の評価のときに変数h変数1i
,
h変数2i,...,
h変数n
iが現れれば 、それらの値は新しく 用意された場所に蓄えられている値が使われます。h本体iの一連の式の評価が終ると局所 変数とその値の関係は捨てられ 、h本体iの最後の式の評価結果を letの値とします。同じ 名前を持つ変数が let の外にあっても、同じ 名前の変数を影響を与えずに letの 中で使うことができます。例を見てみましょう。
> (define x 10)
#<unspecified>
> x 10
> (let ((x 7)) (set! x (* x x)) x) 49
> x 10
上の例ではletの中で x の値をset! によって49 にしていますが 、トップレベル定義 の x に影響していない事がわかります。
ほかにもいくつかの例を見てみましょう。
> (define x 1)
#<unspecified>
> (define y 3)
#<unspecified>
> (let ((a (+ y 1)) (b (* x 2))) (+ a b)) 6
> (let ((x (+ y 1)) (y (* x 2))) (+ x y)) 6
最後の例での局所変数の初期値を決める式(+ y 1) と (* x 2) の評価では、変数 xと y は let の外での値が使われます。
(
それらの値はそれぞれの4
と2
でです。)
このように letは、局所変数の初期値の計算をするのにletの外で定義されている変数の値を使い ます。
3.9.2 let* による局所変数
let*の一般的な形は
4 (let* ((h変数1i h式1i) (h変数2i h式2i)
...
(h変数
n
i h式n
i))h本体i)
です18。
18この形式での使い方は
Scheme
に必須な機能とは定められていません。そのために、処理系によっては 使えない場合がありますが 、多くの処理系では用意されていることが多いようです。let*は let と似て局所変数を用意します。最初の局所変数の初期値を求めると、その 値を次の局所変数の初期値を求めるときに使うことができる点が、letと違うところです。
例を見てみましょう。
> (define x 1)
#<unspecified>
> (define y 3)
#<unspecified>
> (let* ((x (+ y 1)) (y (* x 2))) (+ x y)) 12
(+ y 1) を評価すると
4
なので、これが x の初期値となります。次は (* x 2)を評価して y の初期値を決めますが 、ここでの xは最初の局所変数が使われます。ですので、yの 初期値は
4
となります。letや let*は入れ子にして使うこともできます。次の式を評価すると、ど ういう結果 が得られるでしょうか
?
(let ((x 0)) (let ((x 1))
x))
答えは 1です。変数は、文面上で一番近い定義による束縛による値が使われますので、(let
((x 1) での定義が使われます。このため、 上の式の評価結果は 1になります。