4.9 赋值:set!
The Racket Reference的Assignment: set! and set!-values一节中也提供了set!的文档。
对变量赋值使用 set!:
(set! id expr)
set! 表达式对 expr 进行求值并更改 id (它必须在环境内被绑定)的值为该结果。set! 表达式的结果本身为 #<void>。
(define greeted null)
(define (greet name) (set! greeted (cons name greeted)) (string-append "Hello, " name)) > (greet "Athos") "Hello, Athos"
> (greet "Porthos") "Hello, Porthos"
> (greet "Aramis") "Hello, Aramis"
> greeted '("Aramis" "Porthos" "Athos")
(define (make-running-total) (let ([n 0]) (lambda () (set! n (+ n 1)) n))) (define win (make-running-total)) (define lose (make-running-total))
> (win) 1
> (win) 2
> (lose) 1
> (win) 3
4.9.1 赋值使用准则
尽管有时使用 set! 是恰当的,但 Racket 的风格通常不鼓励使用 set!。以下准则可以帮助你澄清何时使用 set! 是恰当的。
在任何现代语言中,对共享的标识符赋值都不能取代向过程中传入参数或获取其结果。
非常糟糕的示例:(define name "unknown") (define result "unknown") (define (greet) (set! result (string-append "Hello, " name))) > (set! name "John") > (greet) > result "Hello, John"
良好的示例:(define (greet name) (string-append "Hello, " name)) > (greet "John") "Hello, John"
> (greet "Anna") "Hello, Anna"
一系列对局部变量的赋值远不如嵌套的绑定。
糟糕的示例:> (let ([tree 0]) (set! tree (list tree 1 tree)) (set! tree (list tree 2 tree)) (set! tree (list tree 3 tree)) tree) '(((0 1 0) 2 (0 1 0)) 3 ((0 1 0) 2 (0 1 0)))
良好的示例在迭代中使用赋值来累计结果是种糟糕的风格。通过循环参数来累计要更好。
有时比较糟糕的示例:良好的示例(define (sum lst) (let loop ([lst lst] [s 0]) (if (null? lst) s (loop (cdr lst) (+ s (car lst)))))) > (sum '(1 2 3)) 6
更好的示例(使用了既有的函数):绝佳的示例(一种通用的方法):在必须或适于使用带状态对象的情况下,用 set! 来实现对象的状态也是可以的。
Ok example:
其它所有等价的情况下,不使用赋值或可变量的程序总是优于使用赋值或可变量的程序。 然而,即便在副作用可以被避免的情况下,当使用赋值能够显著提升可读性或能够实现 明显更好的算法时,那就应该使用它。
比起直接使用 set! 来说,使用向量或散列表这类可变值能减少 对程序风格的疑虑,然而,简单地将程序中的 set! 替换为 vector-set! 显然无法提升程序的风格。
4.9.2 多重赋值:set!-values
The Racket Reference的Assignment: set! and set!-values一节中也提供了set!-values的文档。
set!-values 形式可一次对多个变量赋值,给定一个产生对应数量的值的表达式:
(set!-values (id ...) expr)
此形式等价于使用 let-values 从 expr 中接收多个结果, 然后使用 set! 将这些结果分别赋予这些 id。