Lisp 动态作用域可以实现词法作用域吗?

Lisp 动态作用域可以实现词法作用域吗?,lisp,common-lisp,lexical-scope,Lisp,Common Lisp,Lexical Scope,旧的Lisp,包括Common Lisp和emacs 24.1之前的elisp,是动态范围的,在我阅读的资源中,一致认为词法范围更适合编程 我觉得动态范围更容易理解。动态范围完全可以从符号及其值的角度来考虑。词法范围需要一个新的“绑定”概念,据我目前所知,它破坏了Lisp语言的一些简单性/优雅性 我一直在思考如何更严格地问这个问题,“词汇范围是否破坏了一些优雅?”我的最佳想法是看看它们是否等效,即一个是否可以实现另一个 是否有可能在动态范围列表中将词汇范围的lisp实现为一个简单的扩展?在没有译

旧的Lisp,包括Common Lisp和emacs 24.1之前的elisp,是动态范围的,在我阅读的资源中,一致认为词法范围更适合编程

我觉得动态范围更容易理解。动态范围完全可以从符号及其值的角度来考虑。词法范围需要一个新的“绑定”概念,据我目前所知,它破坏了Lisp语言的一些简单性/优雅性

我一直在思考如何更严格地问这个问题,“词汇范围是否破坏了一些优雅?”我的最佳想法是看看它们是否等效,即一个是否可以实现另一个

是否有可能在动态范围列表中将词汇范围的lisp实现为一个简单的扩展?在没有译员引入新的特殊符号的情况下,有可能做到这一点吗


更具体地说,可以在动态范围的lisp中实现创建词法范围的
吗?

动态范围更容易实现,也许更容易理解,但它使代码更难阅读:

(setf y 11)

(defun add-some (x)
  (+ x y))

(defun add-ten (x)
  (let ((y 10))
    (add-some x)))
在词法lisp中,您可以看到
y
中的
addsome
是全局变量
y
(add ten 89)
的结果将是
100
,因为
y
add some
中是10,而
add ten
中的本地
y
没有任何作用

如果是动态Lisp,答案将是
99
,lambda的主体可以引用不在全局范围内的变量。它们的结果变得神奇而难以预测。bug可能会忘记绑定它们,或者多个函数会覆盖它,因此最终结果是非常有bug的软件

在Common Lisp中,您有动态变量,为了不出错,可以使用
*耳罩*
来识别它们

在最初的Lisp文件中,John McCarthy在最初的高阶函数
maplist
和用于实现
diff
的匿名函数中使用了相同的变量
x
。结果是他的例子不起作用。我们讨论的是6行代码,变量的重用在动态范围语言中引入了难以发现的bug

从动态范围的Lisp生成词法Lisp

CL-USER 29 > (foo 32)

Error: The variable Y is unbound.
let
的动态作用域将一直存在,直到代码在其中完成执行,并且对let中变量的引用可用于所有调用的函数,除非它们本身被重写。你不会得到闭包,所以如果你想在动态范围的lisp中使用闭包,你需要自己制作框架


Scheme最初是作为动态范围的Lisp(MacLisp)下的解释器编写的。使用宏(可能还有读卡器宏),您可以使动态绑定的Lisp像词汇绑定的Lisp一样工作(或者相反),但它的效率不如一开始就进行词汇绑定

在简单的Lisp代码中,它没有多大区别。动态绑定可能更符合早期Lisp方言的精神。词法绑定更符合函数式编程的精神,我们经常将高阶函数与闭包结合使用

有一些情况有利于词汇绑定

让我们看看动态绑定和特殊变量

自由变量在运行时获得其绑定

CL-USER 29 > (foo 32)

Error: The variable Y is unbound.
Sylvester在他的回答中描述了一个问题:自由变量可以通过动态绑定在运行时获取它们的值

CL-USER 27 > (defun foo (x)
               (declare (special x y))
               (+ x y))
FOO

CL-USER 28 > (let ((y 10)) (declare (special y)) (foo 32))
42

CL-USER 29 > (foo 32)
CL-USER 33 > (let ((x 10)) (declare (special x)) (lambda (y) (declare (special y x)) (+ y x)))
#<anonymous interpreted function 406000C304>

CL-USER 34 > (mapcar * '(1 2 3))

Error: The variable X is unbound.
上述情况可能导致只能在运行时调试的情况。从源代码很难重建
y
将具有哪些值,因为绑定可能发生在调用链中的任意位置。RichardStallman喜欢这个特性——他认为这是一个编辑扩展语言的好选择。CommonLisp对许多事情使用特殊变量,例如在其I/O系统中。例如,流不需要一直通过参数传递

自由变量在运行时可能无法获得其绑定

CL-USER 29 > (foo 32)

Error: The variable Y is unbound.
如果我们不为自由特殊变量定义顶级默认值,我们就不能只调用函数

非闭包

CL-USER 31 > (let ((x 10)) (declare (special x))
               (lambda (y) (declare (special y x)) (+ y x)))
#<anonymous interpreted function 40600011F4>
该函数使用动态绑定

CL-USER 27 > (defun foo (x)
               (declare (special x y))
               (+ x y))
FOO

CL-USER 28 > (let ((y 10)) (declare (special y)) (foo 32))
42

CL-USER 29 > (foo 32)
CL-USER 33 > (let ((x 10)) (declare (special x)) (lambda (y) (declare (special y x)) (+ y x)))
#<anonymous interpreted function 406000C304>

CL-USER 34 > (mapcar * '(1 2 3))

Error: The variable X is unbound.
CL-USER 33>(let((x10))(declare(special x))(lambda(y)(declare(special y y x))(+y x)))
#
CL-USER 34>(地图车*'(1 2 3))
错误:变量X未绑定。
再一次,这不是一个结束,我们不能只是传递它

摘要


我们真的需要一种默认情况下可以使用闭包的语言,在这种语言中,编译器可以警告未绑定的变量,并且在源代码中可以很容易发现大多数绑定。

一旦您知道如何使用它,所有代码都会有很大的不同——简单与否,lisp与否。这不是答案,所以我写它作为一个评论。动态范围很难理解。一方面,您不需要了解闭包(闭包是您应该在这句话中使用的术语,而不是绑定),这意味着闭包更容易实现,而且表面上更容易理解。然而,对于动态范围,对自由变量的任何引用都需要运行代码来了解您得到的值——而且运行代码(在非常深刻和真实的意义上)比仅仅读取代码要难理解得多。Common Lisp,至少在过去与CLtL1一样,在默认情况下一直是词汇范围。