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))