第五章,詞法變量
=====
Scheme的變量有一定的詞法作用域,即它們在程序代碼中只對特定范圍的代碼結構可見。迄今為止我們所見過的全局變量也沒有例外的:它們的作用域是整個程序,這也是一種特定的作用范圍。
我們也碰見過一些示例包含局部變量。它們都是lambda過程的參數,當過程被調用時這些變量會被賦值,而它們的作用域僅限于在過程的內部。例如:
```scheme
(define x 9)
(define add2 (lambda (x) (+ x 2)))
x => 9
(add2 3) => 5
(add2 x) => 11
x => 9
```
這里有一個全局變量`x`,還有一個局部變量`x`,就是在過程`add2`中那個字母`x`。全局變量`x`的值一直是9。第一次調用`add2`過程時,局部的`x`會被賦值為3,而第二次調用`add2`時,局部變量`x`的會被賦值為全局變量`x`的值,即`9`。
當過程的調用結束時,全部變量`x`仍然是9。
而`set!`代碼結構可修改變量的賦值。
```scheme
(set! x 20)
```
上面代碼將全局變量`x`的值9修改為20,因為對于`set!`全局變量是可見的。如果`set!`是在`add2`過程體內被調用,那修改的就是局部變量`x`:
```scheme
(define add2
(lambda (x)
(set! x (+ x 2))
x))
```
這里`set!`在局部變量`x`上加上2,并且會返回局部變量x的新值。(從結果來看,我們無法區分這個過程和先前的`add2`過程)。
我們可以像先前一樣使用全局的`x`做參數值來調用`add2`:
```scheme
(add2 x) => 22
```
(記住全局變量x的值現在是20,而不是9!)
`add2`過程內的`set!`調用僅會影響局部變量x。盡管局部變量x被賦了全局變量x的值,但后者不會因為`set!`為局部變量`x`賦值而受影響。
```scheme
x => 20
```
注意我們做這些討論是因為我們為局部變量和全局變量使用了同樣的標識`x`。在某些代碼中,這個叫`x`的標識符指的是語法閉包中的局部`x`變量,這會暫時隱藏閉包外或全局變量`x`的值。例如,
```scheme
(define counter 0)
(define bump-counter
(lambda ()
(set! counter (+ counter 1))
counter))
```
`bump-counter`是一個沒有參數的過程(沒有參數的過程也稱作`thunk`). 它沒有引入局部變量和參數,這樣就不會隱藏任何值。在每次調用時,它會修改全局變量`counter`的值,讓它增加1,然后返回它當前的值。下面是一些`bump-counter`的成功調用示例:
```scheme
(bump-counter) => 1
(bump-counter) => 2
(bump-counter) => 3
```
## 5.1 let 和 let*
并不是一定要顯式的創建過程才可以創建局部變量。有個特殊的代碼結構let可以創建一列局部變量以便在其結構體中使用:
```scheme
(let ((x 1)
(y 2)
(z 3))
(list x y z))
=> (1 2 3)
```
和`lambda`一樣,在`let`結構體中,局部變量`x`(賦值為1)會暫時隱藏全局變量`x`(賦值為20)。
局部變量`x`、`y`、`z`分別被賦值為1、2、3,這個初始化的過程并不作為`let`過程結構體的一部分。因此,在初始化時對`x`的引用都指向了全局變量`x`,而不是局部變量`x`。
```scheme
(let ((x 1)
(y x))
(+ x y))
=> 21
```
上面代碼中,因為局部變量`x`被賦值為1,而`y`被賦上了值為20的全局變量`x`。
有時候,用`let`依次的創建局變量非常的方便,如果在初始化區域中可以用先創建的變量來為后創建的變量賦值也會非常方便。`let*`結構就可以這樣做:
```scheme
(let* ((x 1)
(y x))
(+ x y))
=> 2
```
在初始化y變量時的x,指的是前面剛創建好的變量x。
這個例子完全等價于下面這個`let`嵌套的程序,更深了說,實際上就是`let`嵌套的縮寫。
```scheme
(let ((x 1))
(let ((y x))
(+ x y)))
=> 2
```
我們也可以把一個過程做為值賦給變量:
```scheme
(let ((cons (lambda (x y) (+ x y))))
(cons 1 2))
=> 3
```
在這個`let`構結體中,變量`cons`將它的參數進行相加。而在`let`結構的外面,`cons`還是用來創建點對。
## 5.2 fluid-let
一個詞法變量如果沒有被隱藏,在它的作用域內一直都為可見狀態。有時候,我們有必要將一個詞法變量臨時的設置為一個固定的值。為此我們可使用`fluid-let`結構(`fluid-let`是一個非標準的特殊結構。可參見8.3,在Scheme中定義fluid-let)。
```scheme
(fluid-let ((counter 99))
(display (bump-counter)) (newline)
(display (bump-counter)) (newline)
(display (bump-counter)) (newline))
```
這和`let`看起來非常相像,但并不是暫時的隱藏了全局變量counter的值,而是在`fluid-let`執行體中臨時的將全局變量`counter`的值設置為了99直到執行體結束。因此執行體中的三句`display`產生了結果
```
100
101
102
```
當`fluid-let`表達式計算結束后,全局變量`counter`會恢復成之前的的值。
```scheme
counter => 3
```
注意`fluid-let`和`let`的效果完全不同。`fluid-let`不會和`let`一樣產生一個新的變量。它會修改已經存的變量的值綁定,當`fluid-let`結束時這個修改也會結束。
為了清楚的說明這一些,可以思考這個根據前一個示例用`let`替換`fluid-let`后的程序。這次的輸出是
```
4
5
6
```
即,初始值為3的全局變量`counter`,被每一次`bump-counter`的調用更新。而新創建的初始值為99的詞法變量`counter`并沒有影響到`bump-counter`的執行,因為盡管`bump-counter`是在局部變量`counter`的作用域內被調用的,但`bump-counter`的結構體并不在這個作用域內。所以`bump-counter`中的`counter`仍然指的是全局變量`counter`,最后的值為6。
```scheme
counter => 6
```