下面的代码,在动态范围假设下,将返回错误。
(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
我在这里想念什么?
下面的代码,在动态范围假设下,将返回错误。
(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
我在这里想念什么?
(define test
(let ((x 10))
(lambda () x)))
这里我们从x
局部变量的作用域返回 lambda 函数。在词法范围内,一个环境被附加到创建的 lambda 函数上。这个环境由在创建 lambda 函数时可用的自由变量之上的绑定变量组成——这里,x
绑定到 10。因此,当调用这个返回的 lambda 函数时,它x
只能是10
。
在动态范围内let
是死代码。创建的 lambda 函数不存储其词法环境,因此当它被调用时,将在调用的实际时间x
重新查找。到那时,用值调用的变量将不再存在。在调用时,lambda 查找的内容将是您绑定的任何内容 :x
10
x
x
(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))))))
在词法作用域中,f
inside(lambda (n) ...)
是未绑定的变量,但在动态作用域中f
首先建立,并且在调用之后(f 5)
它仍然可用于所有嵌套调用(f 4)
,等等,直到对inside(f 3)
的评估完成;只有当那个退出返回结果时,它才会被取消、销毁。(f 5)
(lambda (f) (f 5))
f
lambda
(f 5)
在Paul Grahams 对 McCarthys 原始 lisp 论文的精彩总结中,他提到论文中有一个错误。第一个高阶函数 ,maplist
作为x
列表参数的名称。在演示diff
中,他将一个函数作为参数传递给maplist
该函数。x
这两个在第一对之后发生碰撞,因此由于动态范围而不起作用。动态范围极易出错,并且在所有全局变量都是动态的 Common Lisp 中,*earmuffs*
命名约定是避免无数小时找到改变函数以执行与预期完全不同的事情的全局的必要条件。
使用动态范围,g
将是未定义的,因为第 6 行没有命名变量g
。