4.13 动态绑定:parameterize

+The Racket ReferenceParameters一节中也提供了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! 更重要的优势: