Scheme / Guile:函数内部的可变自重定义

时间:2017-09-06 17:12:37

标签: scheme guile

我觉得理解这种微妙之处可能有助于我理解范围在方案中的作用。

那么,如果你尝试做类似的事情,为什么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”。

那么为什么同样的事情在函数内部发生时会产生运行时错误(它确实支持重新定义)?换句话说:为什么自我重新定义只在函数内部不起作用?

1 个答案:

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