第三章 Forms代碼結構
====
讀者們會發現迄今為止我們提供的Scheme示例程序也都是s-表達式。這對所有的Scheme程序來說都適用:程序是數據。
因此,字符數據`#\c`也是一個程序,或一個代碼結構。我們將使用更通用的說法代碼結構而不是程序,這樣我們也可以處理程序片段。
Scheme計算代碼結構`#\c`得到結果`#\c`,因為`#\c`可以自運算。但不是所有的s-表達式都可以自運算。比如symbol 表達式 `xyz`運算得到的結果是`xyz`這個變量所承載的值;list 表達式`(string->number "16")`運算的結果是數字`16`。(注:之前學過的list類型的數據都是類似`(1 2 3 4 5)`這樣,所以稱`(string->number "16")`為列表s-表達式)
也不是所有的s-表達式都是有效的程序。如果你直接輸入點值對`(1 . 2)`,你將會得到一個錯誤。
Scheme運行一個列表形式的代碼結構時,首先要檢測列表第一個元素,或列表頭。如果這個列表頭是一個過程,則代碼結構的其余部分則被當成將傳遞給這個過程的參數集,而這個過程將接收這些參數并運算。
如果這個代碼結構的列表頭是一個特殊的代碼結構,則將會采用一種特殊的方式來運行。我們已經碰到過的特殊的代碼結構有`begin`, `define`和 `set!`。
`begin`可以讓它的子結構可以有序的運算,而最后一個子結構的結果將成為整個代碼結構的運行結果。`define`會聲明并會初始化一個變量。`set!` 可以給已經存在的變量重新賦值。
## 3.1 Procedures(過程)
我們已經見過了許多系統過程,比如,`cons`, `string->list`等。用戶可以使用代碼結構`lambda`來創建自定義的過程。例如,下面定義了一個過程可以在它的參數上加上2:
```scheme
(lambda (x) (+ x 2))
```
第一個子結構,`(x)`,是參數列表。其余的子結構則構成了這個過程執行體。這個過程可以像系統過程一樣,通過傳遞一個參數完成調用:
```scheme
((lambda (x) (+ x 2)) 5)
=> 7
```
如果我們希望能夠多次調用這個相同的過程,我們可以每次使用`lambda`重新創建一個復制品,但我們有更好的方式。我們可以使用一個變量來承載這個過程:
```scheme
(define add2
(lambda (x) (+ x 2)))
```
只要需要,我們就可以反復使用`add2`為參數加上2:
```scheme
(add2 4) => 6
(add2 9) => 11
```
譯者注:定義過程還可以有另一種簡單的方式,直接用define而不使用lambda來創建:
```scheme
(define (add2 x)
(+ x 2))
```
### 3.1.1 過程的參數
`lambda` 過程的參數由它的第一個子結構(緊跟著`lambda`標記的那個結構)來定義。`add2`是一個單參數或一元過程,所以它的參數列表是只有一個元素的列表`(x)`。標記`x`作為一個承載過程參數的變量而存在。在過程體中出現的所有`x`都是指代這個過程的參數。對這個過程體來說x是一個局部變量。
我們可以為兩個參數的過程提供兩個元素的列表做參數,通常都是為n個參數的過程提供n個元素的列表。下面是一個可以計算矩形面積的雙參數過程。它的兩個參數分別是矩形的長和寬。
```scheme
(define area
(lambda (length breadth)
(* length breadth)))
```
我們看到`area`將它的參數進行相乘,系統過程`*`也可以實現相乘。我們可以簡單的這樣做:
```scheme
(define area *)
```
### 3.1.2 可變數量的參數(不定長參數)
有一些過程可以在不同的時候傳給它不同個數的參數來完成調用。為了實現這樣的過程,`lambda`表達式列表形式的參數要被替換成單個的符號。這個符號會像一個變量一樣來承載過程調用時接收到的參數列表。
通常,`lambda`的參數列表可以是一個列表構結`(x …)`,一個符號,或者`(x … . z)`這樣的一個點對結構。
當參數是一個點對結構時,在點之前的所有變量將一一對應過程調用時的前幾個參數,點之后的那個變量會將剩余的參數值作為一個列表來承載。
<!-- 譯者注:以下是幾種可變參數的寫法: -->
<!-- ((lambda (x y z) -->
<!-- (begin -->
<!-- (display x) -->
<!-- (newline) -->
<!-- (display y) -->
<!-- (newline) -->
<!-- (display z) -->
<!-- (newline) ) ) -->
<!-- 1 2 3) -->
<!-- 和 -->
<!-- (define (t x y z) -->
<!-- (begin -->
<!-- (display x) -->
<!-- (newline) -->
<!-- (display y) -->
<!-- (newline) -->
<!-- (display z) -->
<!-- (newline) ) ) -->
<!-- (t 1 2 3) -->
<!-- 等價。 -->
<!-- ((lambda x -->
<!-- (begin -->
<!-- (display x) ) ) -->
<!-- 1 2 3 4) -->
<!-- 和 -->
<!-- (define (t . x) -->
<!-- (begin -->
<!-- (display x) ) ) -->
<!-- (t 1 2 3 4) -->
<!-- 等價。 -->
<!-- ((lambda (x . y) -->
<!-- (begin -->
<!-- (display x) -->
<!-- (newline) -->
<!-- (display y) -->
<!-- (newline) ) ) -->
<!-- 1 2 3 4 5) -->
<!-- 和 -->
<!-- (define (t x . y) -->
<!-- (begin -->
<!-- (display x) -->
<!-- (newline) -->
<!-- (display y) -->
<!-- (newline) ) ) -->
<!-- (t 1 2 3 4 5) -->
<!-- 等價。 -->
## 3.2 apply過程
`apply`過程允許我們直接傳遞一個裝有參數的list 給一個過程來完成對這個過程的批量操作。
```scheme
(define x '(1 2 3))
(apply + x)
=> 6
```
通常,`apply`需要傳遞一個過程給它,后面緊接著是不定長參數,但最后一個參數值一定要是list。它會根據最后一個參數和中間其它的參數來構建參數列表。然后返回根據這個參數列表來調用過程得到的結果。例如:
```scheme
(apply + 1 2 3 x)
=> 12
```
## 3.3 順序執行
我們使用`begin`這個特殊的結構來對一組需要有序執行的子結構來進行打包。許多Scheme的代碼結構都隱含了`begin`。例如,我們定義一個三個參數的過程來輸出它們,并用空格間格。一種正確的定義是:
```scheme
(define display3
(lambda (arg1 arg2 arg3)
(begin
(display arg1)
(display " ")
(display arg2)
(display " ")
(display arg3)
(newline))))
```
在Scheme中,lambda的語句體都是隱式的`begin`代碼結構。因此,`display3`語句體中的begin不是必須的,不寫時也不會有什么影響。
`display3`更簡化的寫法是:
```scheme
(define display3
(lambda (arg1 arg2 arg3)
(display arg1)
(display " ")
(display arg2)
(display " ")
(display arg3)
(newline)))
```