4.13 动态绑定:parameterize
The Racket Reference的Parameters一节中也提供了parameterize的文档。
parameterize 形式会在 body 表达式求值时将一个新值与一个 参量相关联:
(parameterize ([parameter-expr value-expr] ...) body ...+)
术语“参量(Parameter)”有时也用来指代函数的参数,不过这里描述的 Racket 中的“参量”拥有更加特定的意思。
例如 error-print-width 参量用于控制错误信息中打印的字符个数:
> (parameterize ([error-print-width 5]) (car (expt 10 1024))) car: contract violation
expected: pair?
given: 10...
> (parameterize ([error-print-width 10]) (car (expt 10 1024))) car: contract violation
expected: pair?
given: 1000000...
更一般地说,参量实现了一种动态绑定。make-parameter 函数接受任何值并返回 一个新的以给定值初始化的参量。将参量作为函数来应用会返回其当前值:
> (define location (make-parameter "here")) > (location) "here"
在 parameterize 形式中,每个 parameter-expr 必须产生一个参量。
在 body 的求值过程中,每一个具体的参量都会被给定其对应
value-expr 的结果。当控制流离开 parameterize 形式—
> (parameterize ([location "there"]) (location)) "there"
> (location) "here"
> (parameterize ([location "in a house"]) (list (location) (parameterize ([location "with a mouse"]) (location)) (location))) '("in a house" "with a mouse" "in a house")
> (parameterize ([location "in a box"]) (car (location))) car: contract violation
expected: pair?
given: "in a box"
> (location) "here"
parameterize 形式并不是像 let 那样的绑定形式。前面每次使用 location 都直接引用了其原始定义。parameterize 形式会在整个 parameterize 主体的求值过程中调整参量的值,即便对文本上在 parameterize 之外的参量也是如此:
> (define (would-you-could-you?) (and (not (equal? (location) "here")) (not (equal? (location) "there")))) > (would-you-could-you?) #f
> (parameterize ([location "on a bus"]) (would-you-could-you?)) #t
如果一个参量从文本上看在 parameterize 的主体中使用,但并未在 parameterize 形式产生一个值之前求值,那么该次使用并不会观测到 parameterize 为它设置的值:
> (let ([get (parameterize ([location "with a fox"]) (lambda () (location)))]) (get)) "here"
参量的当前绑定可通过将该参量作为函数调用并传入一个值来命令式地调整。若一个 parameterize 已经调整了参量的值,那么直接应用该参量过程只会影响与活动的 parameterize 相关联的值。
> (define (try-again! where) (location where)) > (location) "here"
> (parameterize ([location "on a train"]) (list (location) (begin (try-again! "in a boat") (location)))) '("on a train" "in a boat")
> (location) "here"
通常使用 parameterize 是命令式地更新参量值的更好的方式。 而要更新用 let 绑定的全新的变量则更适合使用 set!(见 赋值:set!)。
很多参量可以解决的问题,用变量和 set! 似乎同样可以解决。例如 lokation 可以定义为字符串,而 set! 可用于调整其值:
> (define lokation "here")
> (define (would-ya-could-ya?) (and (not (equal? lokation "here")) (not (equal? lokation "there")))) > (set! lokation "on a bus") > (would-ya-could-ya?) #t
然而,参量提供了比几点 set! 更重要的优势:
parameterize 形式能够在控制流因异常而退出时帮助自动重置参量的值。 添加异常处理和其它形式来撤销 set! 的作为相对更加繁琐些。
参量可以和尾调用很好地配合(见 尾递归)。 parameterize 形式中的最后一个 body 即在其对应 parameterize 形式的尾部。
参量也可以和线程协同工作(见 Threads)。 parameterize 形式只会为当前线程中的求值调整参量的值, 这样可以避免和其它线程的竞争条件。