我觉得理解这种微妙之处可能有助于我理解范围在方案中的作用。
那么,如果你尝试做类似的事情,为什么Scheme会出错:
(define (func n)
(define n (+ 1 n))
n)
调用函数时,它只会在运行时出错。
我觉得奇怪的原因是因为Scheme确实允许重新定义,甚至在函数内部。例如,这不会给出错误,并且总是按预期返回值5:
(define (func n)
(define n 5)
n)
此外,Scheme似乎也支持全球空间的自我重新定义。例如:
(define a 5)
(define a (+ 1 a))
没有错误,导致“a”按预期显示“6”。
那么为什么同样的事情在函数内部发生时会产生运行时错误(它确实支持重新定义)?换句话说:为什么自我重新定义只在函数内部不起作用?
答案 0 :(得分:2)
define
首先,顶级程序由实现的不同部分处理而不是在函数中处理,并且不允许定义已定义的变量。
(define a 10)
(define a 20) ; ERROR: duplicate definition for identifier
它可能会在REPL中运行,因为重新定义东西是很常见的,但是在运行程序时这绝对是禁止的。在R5RS之前和之前发生的事情都没有明确规定并且没有关注,因为违反规范它不再是一个计划程序,实施者可以自由地做任何他们想做的事情。结果当然是许多未指明的东西得到特定于实现的行为,这些行为不可移植或不稳定。
<强>解决方案:强>
set!
改变绑定:
(define a 10)
(set! a 20)
lambda中的define
(函数,let,...) lambda中的define
是完全不同的东西,由完全不同的实现部分处理。它由宏/特殊表单lambda
处理,以便将其重写为letrec*
letrec*
或letrec
用于使函数递归,因此在计算表达式时名称必须可用。因此,当您define
n
时,它已经隐藏了您作为参数传递的n
。此外,R6RS实现者需要在评估尚未初始化的绑定时发出错误信号,这可能发生了什么。在R6RS实施者可以自由地做任何他们想做的事之前:
(define (func n)
(define n (+ n 1)) ; illegal since n hasn't got a value yet!
n)
这实际上变成了:
(define func
(lambda (n)
(let ((n 'undefined-blow-up-if-evaluated))
(let ((tmpn (+ n 1)))
(set! n tmpn))
n)))
现在编译器可能会发现它在编译时违反了规范,但许多实现在运行之前都不知道。
(func 5) ; ==> 42
如果实施者对书籍有良好的品味,那么R5RS的结果非常好。
您说的版本的不同之处在于,这并不违反在正文之前评估n
的规则:
(define (func n)
(define n 5)
n)
变为:
(define func
(lambda (n)
(let ((n 'undefined-blow-up-if-evaluated))
(let ((tmpn 5)) ; n is never evaluated here!
(set! n tmpn))
n)))
<强>解决方案强>
使用非冲突名称
(define (func n)
(define new-n (+ n 1))
new-n)
使用let
。在评估表达式时,它没有自己的绑定:
(define (func n)
(let ((n (+ n 1)))
n))