Scheme/Guile:函数内部变量自重定义
Scheme/Guile: Variable self-re-definition inside a function
我觉得理解这个微妙之处可能有助于我理解作用域在 Scheme 中的工作原理。
那么,如果您尝试执行类似以下操作,为什么 Scheme 会出错:
(define (func n)
(define n (+ 1 n))
n)
调用函数时只会在运行时出错。
我觉得奇怪的原因是因为 Scheme 确实允许重新定义,甚至是在函数内部。例如,这不会给出错误,并且总是 return 预期的值 5:
(define (func n)
(define n 5)
n)
另外,Scheme好像也支持全局自重定义space。例如:
(define a 5)
(define a (+ 1 a))
没有错误并导致 "a" 按预期显示“6”。
那么,为什么同样的事情发生在函数内部(支持重新定义)时会出现运行时错误?换句话说:为什么自重定义只在函数内部不起作用?
全球define
首先,顶级程序由与函数不同的实现部分处理,并且不允许定义已定义的变量。
(define a 10)
(define a 20) ; ERROR: duplicate definition for identifier
它可能会在 REPL 中工作,因为重新定义东西很常见,但是当 运行 编程时,这是绝对禁止的。在 R5RS 和之前发生的事情是未指定的并且不在乎因为违反规范它不再是 Scheme 程序并且实施者可以自由地做他们想做的事。结果当然是许多未指定的东西会获得特定于实现的行为,这些行为不可移植或不稳定。
解法:
set!
改变绑定:
(define a 10)
(set! a 20)
define
在 lambda 中(函数、let、...)
A define
在 lambda 中是完全不同的东西,由完全不同的实现部分处理。它由 macro/special 形式 lambda
处理,因此它被重写为 letrec*
A 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))
我觉得理解这个微妙之处可能有助于我理解作用域在 Scheme 中的工作原理。
那么,如果您尝试执行类似以下操作,为什么 Scheme 会出错:
(define (func n)
(define n (+ 1 n))
n)
调用函数时只会在运行时出错。
我觉得奇怪的原因是因为 Scheme 确实允许重新定义,甚至是在函数内部。例如,这不会给出错误,并且总是 return 预期的值 5:
(define (func n)
(define n 5)
n)
另外,Scheme好像也支持全局自重定义space。例如:
(define a 5)
(define a (+ 1 a))
没有错误并导致 "a" 按预期显示“6”。
那么,为什么同样的事情发生在函数内部(支持重新定义)时会出现运行时错误?换句话说:为什么自重定义只在函数内部不起作用?
全球define
首先,顶级程序由与函数不同的实现部分处理,并且不允许定义已定义的变量。
(define a 10)
(define a 20) ; ERROR: duplicate definition for identifier
它可能会在 REPL 中工作,因为重新定义东西很常见,但是当 运行 编程时,这是绝对禁止的。在 R5RS 和之前发生的事情是未指定的并且不在乎因为违反规范它不再是 Scheme 程序并且实施者可以自由地做他们想做的事。结果当然是许多未指定的东西会获得特定于实现的行为,这些行为不可移植或不稳定。
解法:
set!
改变绑定:
(define a 10)
(set! a 20)
define
在 lambda 中(函数、let、...)
A define
在 lambda 中是完全不同的东西,由完全不同的实现部分处理。它由 macro/special 形式 lambda
处理,因此它被重写为 letrec*
A 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))