Mutable Bindings.

Racket 支持使用 assignment statement set! (set-bang) 来 mutate bindings。

注意区分 mutate 与 shadow:若 x 在环境中,使用 (set! x 13)mutate the binding so that x maps to the value 13. Doing so affects all code that has this x in its environment. 这也就是说,在 mutate x 之后,在所有的环境中 (而不仅仅是 mutate 之后的环境),x 都将与 13 对应。

1
2
3
4
5
6
(define b 3)
(define f (lambda (x) (* 1 (+ x b))))
(define c (+ b 4))
(set! b 5)
(define z (f 4))
(define w c)

在上例中,我们依次对 bindings 进行求值 (就和在 ML 中一样):

  • b bound to 3.
  • f bound to (lambda (x) (* 1 (+ x b))): 正如之前强调的,在 f 被调用之前不会执行函数体。
  • c: 在环境中寻找 b => b bound to 3 => c bound to 7.
  • mutate b: 此时在所有环境中 b bound to 5.
  • z: 调用函数 f => 参数 x bound to 4 => 在环境中寻找 b => b bound to 5 => z bound to 9.
  • w: 在环境中寻找 c => c bound to 7 => w bound to 7.

在 functional programming 中,使用 mutation 通常是非常 error-prone 的。举例来说,可能函数 f 在定义时想要使用的是 b 之前的值。然而 b 在之后发生了 mutate,这时调用 f 结果会与预期不一致。

General technique in software development. If something might get mutated and you need the old value, make a copy before mutation can occur.

1
2
3
(define f
(let ([b b])
(lambda (x) (* 1 (+ x b)))))

This code makes the b in the function body refer to a local b that is initialized to the global b.