"参数化"在DrScheme中做了什么?

Cla*_*diu 6 scheme functional-programming racket

我试图理解这里的示例代码(下面的例子).我不明白参数化构造.它的文档在这里,但它们没有帮助.它有什么作用?

Eli*_*lay 13

parameterize用于具有"动态范围"的值.你得到一个参数make-parameter.参数本身就像一个函数:在没有输入的情况下调用它并获得它的值,用一个值调用它并设置值.例如:

> (define p (make-parameter "blah"))
> (p)
"blah"
> (p "meh")
> (p)
"meh"
Run Code Online (Sandbox Code Playgroud)

许多函数(包括许多原始函数)使用参数作为定制其行为的方式.例如,printf将使用作为current-output-port参数值的端口打印内容.现在,假设你有一些打印东西的功能:

> (define (foo x) (printf "the value of x is ~s\n"))
Run Code Online (Sandbox Code Playgroud)

您通常会调用此函数并在屏幕上看到打印的内容 - 但在某些情况下,您希望使用它来将某些内容打印到文件或其他内容.你可以这样做:

(define (bar)
  (let ([old-stdout (current-output-port)])
    (current-output-port my-own-port)
    (foo some-value)
    (current-output-port old-stdout)))
Run Code Online (Sandbox Code Playgroud)

这样做的一个问题是做起来很繁琐 - 但是用宏来解决这个问题很容易.(实际上,PLT仍然有一个在某些语言中执行此操作的构造:fluid-let.)但是这里存在更多问题:如果调用foo导致运行时错误会发生什么?这可能会使系统处于不良状态,所有输出都会进入您的端口(您甚至不会看到问题,因为它不会打印任何内容).对此的解决方案(也fluid-let使用)是保护参数的保存/恢复dynamic-wind,这可以确保如果出现错误(如果您知道延续的话,还有更多),那么该值仍然可以恢复.

所以问题是有什么意义的参数而不是只使用全局变量fluid-let?只有全局变量还有两个问题无法解决.一个是当你有多个线程时会发生什么 - 在这种情况下,临时设置值会影响其他线程,这可能仍然需要打印到标准输出.参数通过为每个线程设置特定值来解决此问题.会发生什么是每个线程从创建它的线程"继承"该值,并且一个线程中的更改仅在该线程中可见.

另一个问题更微妙.假设您有一个带有数值的参数,并且您想要执行以下操作:

(define (foo)
  (parameterize ([p ...whatever...])
    (foo)))
Run Code Online (Sandbox Code Playgroud)

在Scheme中,"尾调用"很重要 - 它们是创建循环的基本工具等等. parameterize做一些魔法,允许它暂时更改参数值但仍然保留这些尾调用.例如,在上面的例子中,你得到一个无限循环,而不是得到一个堆栈溢出错误 - 会发生什么是这些parameterize表达式中的每一个都可以以某种方式检测何时有更早的parameterize不再需要进行清理.

最后,parameterize实际上使用PLT的两个重要部分来完成它的工作:它使用线程单元来实现每线程值,并使用连续标记来保留尾调用.这些功能中的每一个本身都是有用的.