跳转到内容

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 实现中,它可能会运行得更慢。)

华夏公益教科书