3 回答
parameterize
用于具有“动态范围”的值。你得到一个参数make-parameter
。参数本身就像一个函数:在没有输入的情况下调用它,你会得到它的值,用一个值调用它,它会设置值。例如:
> (define p (make-parameter "blah"))
> (p)
"blah"
> (p "meh")
> (p)
"meh"
许多函数(包括许多原始函数)使用参数作为自定义其行为的一种方式。例如printf
,将使用作为参数值的端口打印内容current-output-port
。现在,假设您有一些打印某些内容的函数:
> (define (foo x) (printf "the value of x is ~s\n"))
您通常调用此函数并在屏幕上看到打印的内容——但在某些情况下,您希望使用它将某些内容打印到文件或其他内容中。你可以这样做:
(define (bar)
(let ([old-stdout (current-output-port)])
(current-output-port my-own-port)
(foo some-value)
(current-output-port old-stdout)))
这样做的一个问题是做起来很乏味——但这很容易用宏解决。(事实上,PLT 仍然有一个在某些语言中可以做到这一点的结构:fluid-let
。)但是这里还有更多问题:如果调用foo
导致运行时错误会发生什么?这可能会使系统处于不良状态,所有输出都进入您的端口(您甚至不会看到问题,因为它不会打印任何内容)。一个解决方案(也fluid-let
使用)是用 来保护参数的保存/恢复dynamic-wind
,这确保如果有错误(以及更多,如果你知道延续),那么值仍然会被恢复。
所以问题是使用参数而不是仅仅使用全局变量和的意义fluid-let
何在?还有两个问题是仅用全局变量无法解决的。一种是当您有多个线程时会发生什么——在这种情况下,临时设置该值会影响其他线程,这些线程可能仍希望打印到标准输出。参数通过为每个线程设置一个特定的值来解决这个问题。发生的情况是每个线程“继承”创建它的线程的值,并且一个线程中的更改仅在该线程中可见。
另一个问题更微妙。假设您有一个带有数值的参数,并且您想要执行以下操作:
(define (foo)
(parameterize ([p ...whatever...])
(foo)))
在 Scheme 中,“尾调用”很重要——它们是创建循环等的基本工具。 parameterize
做了一些魔术,允许它暂时更改参数值,但仍保留这些尾调用。例如,在上述情况下,您将得到一个无限循环,而不是堆栈溢出错误——发生的情况是,这些表达式中的每一个都可以以某种方式检测何时有一个不再需要进行清理parameterize
的较早的。parameterize
最后,parameterize
实际上使用 PLT 的两个重要部分来完成它的工作:它使用线程单元来实现每个线程的值,它使用连续标记来保存尾调用。这些功能中的每一个本身都是有用的。
parameterize
在块的持续时间内将特定参数设置为指定值,而不影响其外部的值。
参数化是一种方法,您可以通过它在现有函数中动态重新绑定值,而无需使用 lambda 来执行此操作。在实践中,有时使用参数化在函数中重新绑定值比需要传递参数并使用 lambda 绑定它们要容易得多。
例如,假设您使用的库将 HTML 发送到标准输出,但为了方便起见,您希望将该值捕获到字符串并对其执行进一步的操作。库设计者至少有两个选择可以让您轻松完成:1) 接受输出端口作为函数的参数,或 2) 参数化当前输出端口值。1是丑陋和麻烦。2 更好,因为最可能的行为是打印到标准输出,但如果您想打印到字符串端口,您可以参数化对该函数的调用。