[TOC=2]
## 6.1?簡介
在前面的章節中,我已經講述了如何定義函數。在本節中,我講介紹局部變量,這將會使定義函數變得更加容易。
## 6.2?let表達式
使用`let`表達式可以定義局部變量。格式如下:
~~~
(let binds body)
~~~
變量在`binds`定義的形式中被聲明并初始化。`body`由任意多個S-表達式構成。`binds`的格式如下:
~~~
[binds] → ((p1 v1) (p2 v2) ...)
~~~
聲明了變量`p1`、`p2`,并分別為它們賦初值`v1`、`v2`。變量的**作用域(Scope)**為`body`體,也就是說變量只在`body`中有效。
> 例1:聲明局部變量`i`和`j`,將它們與`1`、`2`綁定,然后求二者的和。
~~~
(let ((i 1) (j 2))
(+ i j))
;Value: 3
~~~
`let`表達式可以嵌套使用。
> 例2:聲明局部變量`i`和`j`,并將分別將它們與`1`和`i+2`綁定,然后求它們的乘積。
~~~
(let ((i 1))
(let ((j (+ i 2)))
(* i j)))
;Value: 3
~~~
由于變量的作用域僅在`body`中,下列代碼會產生錯誤,因為在變量`j`的作用域中沒有變量`i`的定義。
~~~
(let ((i 1) (j (+ i 2)))
(* i j))
;Error
~~~
`let*`表達式可以用于引用定義在同一個綁定中的變量。實際上,`let*`只是嵌套的`let`表達式的語法糖而已。
~~~
(let* ((i 1) (j (+ i 2)))
(* i j))
;Value: 3
~~~
> 例3:函數`quadric-equation`用于計算二次方程。它需要三個代表系數的參數:`a`、`b`、`c`?(`ax^2 + bx + c = 0`),返回一個存放答案的實數表。通過逐步地使用`let`表達式,可以避免不必要的計算。
~~~
;;;The scopes of variables d,e, and f are the regions with the same background colors.
(define (quadric-equation a b c)
(if (zero? a)
'error ; 1
(let ((d (- (* b b) (* 4 a c)))) ; 2
(if (negative? d)
'() ; 3
(let ((e (/ b a -2))) ; 4
(if (zero? d)
(list e)
(let ((f (/ (sqrt d) a 2))) ; 5
(list (+ e f) (- e f)))))))))
(quadric-equation 3 5 2) ; solution of 3x^2+5x+2=0
;Value 12: (-2/3 -1)
~~~
> 這個函數的行為如下:
>
> 1. 如果二次項系數`a`為`0`,函數返回`'error`。
> 2. 如果`a ≠ 0`,則將變量`d`與判別式`(b^2 - 4ac)`的值綁定。
> 3. 如果`d`為負數,則返回`'()`。
> 4. 如果`d`不為負數,則將變量`e`與`-b/2a`綁定。
> 5. 如果`d`為`0`,則返回一個包含`e`的表。
> 6. 如果`d`是正數,則將變量`f`與`√(d/2a)`綁定,并返回由`(+ e f)`和`(- e f)`> 構成的表。
實際上,`let`表達式只是`lambda`表達式的一個語法糖:
~~~
(let ((p1 v1) (p2 v2) ...) exp1 exp2 ...)
;?
((lambda (p1 p2 ...)
exp1 exp2 ...) v1 v2)
~~~
這是因為`lambda`表達式用于定義函數,它為變量建立了一個作用域。
> 練習1
>
> 編寫一個解決第四章練習1的函數,該函數旨在通過一個初始速度`v`和與水平面所成夾角`a`來計算飛行距離。
## 6.3?總結
本節中,我介紹了`let`表達式,`let`表達式是`lambda`表達式的一個語法糖。變量的作用域通過使用`let`表達式或`lambda`表達式來確定。在Scheme中,這個有效域由源代碼的編寫決定,這叫做**詞法閉包(lexical closure)**。
## 6.4?習題解答
### 6.4.1?答案1
~~~
(define (throw v a)
(let ((r (/ (* 4 a (atan 1.0)) 180)))
(/ (* 2 v v (cos r) (sin r)) 9.8)))
~~~