1

下面的代码,在动态范围假设下,将返回错误。

(let ((f (lambda (g)
  (lambda (n)
    (if (zero? n)
     1
     (* n ((g g) (- n 1))))))))
 ((f f) 5))

我的答案是 0,因为:

 n*(n-1)*(n-2)*(n-3)*(n-3)*(n-4)*1;; since the call with n=0, n bound to 0
 0*0*0*0*1

我在这里想念什么?

4

2 回答 2

2
(define test
  (let ((x 10))
    (lambda () x)))

这里我们从x局部变量的作用域返回 lambda 函数。在词法范围内,一个环境被附加到创建的 lambda 函数上。这个环境由在创建 lambda 函数时可用的自由变量之上的绑定变量组成——这里,x绑定到 10。因此,当调用这个返回的 lambda 函数时,它x只能是10

在动态范围内let是死代码。创建的 lambda 函数存储其词法环境,因此当它被调用时,将在调用的实际时间x重新查找。到那时,用值调用的变量将不再存在。在调用时,lambda 查找的内容将是您绑定的任何内容 :x10xx

(let ((x 20))
  (test))
; ==> 20 

而且当然:

(test); == ERROR: Unbound variable x

因此,对于您的代码,这是同样的问题。无论g何时(lambda (n) ...)评估,创建 lambda 函数,在返回该 lambda 函数时超出范围,因此当调用返回的 lambda 函数时,g将重新查找,并且将是g调用时绑定的任何内容, 像之前一样。为了使它在动态范围内工作,您可以这样做:

(let ((f (lambda (g n)
             (if (zero? n)
                 1
                 (* n (g g (- n 1)))))))
  (f f 5))

这里的区别是g永远不会超出范围。这适用于动态和词法范围。您可以将其简化为动态范围,如下所示:

(let ((f (lambda (n)                          ; ((lambda (f) (f 5))
             (if (zero? n)                    ;  (lambda (n)
                 1                            ;    (if (zero? n) 
                 (* n (f (- n 1)))))))        ;      1
  (f 5))                                      ;       (* n (f (- n 1))))))

在词法作用域中,finside(lambda (n) ...)是未绑定的变量,但在动态作用域中f首先建立,并且在调用之后(f 5)它仍然可用于所有嵌套调用(f 4),等等,直到对inside(f 3)的评估完成;只有当那个退出返回结果时,它才会被取消、销毁。(f 5)(lambda (f) (f 5))flambda(f 5)

Paul Grahams 对 McCarthys 原始 lisp 论文的精彩总结中,他提到论文中有一个错误。第一个高阶函数 ,maplist作为x列表参数的名称。在演示diff中,他将一个函数作为参数传递给maplist该函数。x这两个在第一对之后发生碰撞,因此由于动态范围而不起作用。动态范围极易出错,并且在所有全局变量都是动态的 Common Lisp 中,*earmuffs*命名约定是避免无数小时找到改变函数以执行与预期完全不同的事情的全局的必要条件。

于 2018-02-24T21:18:49.707 回答
0

使用动态范围,g将是未定义的,因为第 6 行没有命名变量g

于 2018-02-24T19:49:10.237 回答