Scheme 程式設計/可變性
可變性是程式設計中常見的副作用。改變一個已經定義的變數意味著改變它的值。例如,
(define x 3) x
將評估為 3。但是,假設 x 被改變為 5。那麼評估 x 將返回 5。
在 Scheme 中,使用 set! 過程來改變變數。感嘆號(通常被稱為“歎號”)是為了警告程式設計師呼叫 set! 過程會導致副作用;這是 Scheme 函式命名的一種常見做法。例如,我們可以透過以下方式將 x 從 3 改變為 5
(define x 3)
x
(set! x 5)
x
這將導致以下結果
3
5
呼叫 set! 過程後,所有對 x 的未來引用將使用其新的值 5 而不是其舊值 3。嘗試對未定義的識別符號或超出範圍的識別符號呼叫 set! 會導致錯誤。
一些 Scheme 程式設計師不鼓勵使用 set!,這體現在它的命名上。在許多語言中,可變性通常比遞迴更常見(特別是那些容易發生堆疊溢位的語言),但這在 Scheme 中並不一定是這種情況。事實上,一些 Scheme 的實現甚至在使用可變性而不是遞迴來設計算法時效率更低。這是許多 Scheme 程式設計師依賴遞迴的一個原因。標準 Scheme 也需要正確地處理尾呼叫最佳化,這也有助於防止堆疊溢位。有些人還認為純函式式程式(那些不使用可變性或副作用的程式)更容易正式證明其正確性。
set! 函式有幾種變體。set-car! 和 set-cdr! 函式分別改變一個配對的 car 和 cdr 值。
(define x (cons 3 4))
x
(set-car! x 5)
x
(set-cdr! x 6)
x
(set! x (list 3 4))
x
(set-car! x 9)
x
(set-cdr! x (list 4 5))
x
這將產生
(3 . 4)
(5 . 4)
(5 . 6)
(3 4)
(9 4)
(9 4 5)
如前所述,Scheme 中並不總是需要可變性。在 Scheme 中,使用遞迴來完成許多簡單的任務比使用可變性更自然。考慮以下 list-max 的版本
(define list-max
(lambda (lst)
(cond
((equal? '() lst) -inf.0)
(else (find-list-max (cdr lst) (car lst))))))
(define find-list-max
(lambda (lst max-so-far)
(cond
((equal? '() lst) max-so-far)
(else (find-list-max (cdr lst) (max (car lst) max-so-far))))))
對於沒有程式設計經驗的使用者來說,這個版本很容易理解,因此他們沒有變數的概念。另一方面,這個 find-list-max 版本看起來相當笨拙
(define find-list-max
(lambda (lst max-so-far)
(cond
((equal? '() lst) max-so-far)
(else
(set! max-so-far (max max-so-far (car lst)))
(set! lst (cdr lst))
(find-list-max lst max-so-far)))))
它需要 3 行額外的程式碼來完成相同任務,但在速度或清晰度方面沒有取得任何進展。(事實上,在某些 Scheme 實現中,它可能會執行得更慢。)