我之前学过Clojure,非常喜欢这门语言。我也喜欢Emacs,并用Emacs Lisp攻击了一些简单的东西。有一件事使我无法在精神上做任何与Elisp有关的事情。这是动态范围的概念。我只是害怕它,因为它对我来说太陌生,闻起来像半全局变量。
因此,对于变量声明,我不知道哪些事情是安全的,哪些是危险的。根据我的理解,使用setq设置的变量属于动态范围(是吗?)让变量怎么样?在某个地方我读过,让你可以做一些简单的词法范围,但在其他地方我读过,让vars也是动态范围的。
我最担心的是我的代码(使用setq或let)意外地从我调用的平台或第三方代码中破坏了一些变量,或者在调用之后我的局部变量意外地搞砸了。我怎么能避免这个?
我是否可以遵循一些简单的经验法则,确切地知道范围会发生什么,而不会被某种奇怪的,难以调试的方式所困扰?
答案 0 :(得分:45)
这并不差。
主要问题可能出现在函数中的“自由变量”中。
(defun foo (a)
(* a b))
在上面的函数中,a
是一个局部变量。 b
是一个自由变量。在具有动态绑定的系统(如Emacs Lisp)中,b
将在运行时查找。现在有三种情况:
b
未定义 - >错误b
是由当前动态范围中的某个函数调用绑定的局部变量 - >拿这个值b
是一个全局变量 - >拿这个值问题可能是:
在带有编译器的Lisp中,编译上述函数可能会生成一个警告,指出有一个自由变量。通常,Common Lisp编译器会这样做。解释器不会提供该警告,只会在运行时看到效果。
<强>建议强>:
*foo-var*
不要写
(defun foo (a b)
...
(setq c (* a b)) ; where c is a free variable
...)
写:
(defun foo (a b)
...
(let ((c (* a b)))
...)
...)
绑定您想要使用的所有变量,并且您希望确保它们不会绑定到其他位置。
基本上就是这样。
由于GNU Emacs版本24的词汇绑定在其Emacs Lisp中得到支持。请参阅:Lexical Binding, GNU Emacs Lisp Reference Manual。
答案 1 :(得分:13)
我是否可以遵循一些简单的经验法则,确切地知道范围发生了什么,而不会被一些奇怪的,难以调试的方式所困扰?
阅读Emacs Lisp Reference,您将获得许多类似这样的详细信息:
以下是一个例子:
(defun foo () (setq tata "foo"))
(defun bar (tata) (setq tata "bar"))
(foo)
(message tata)
===> "foo"
(bar tata)
(message tata)
===> "foo"
答案 2 :(得分:13)
除了Gilles答案的最后一段,以下是可扩展系统中的RMS argues in favor of dynamic scoping:
一些语言设计师认为 应该避免动态绑定,并且 显式参数传递应该是 用来代替。想象一下功能A. 绑定变量FOO,并调用 功能B,调用该功能 C和C使用FOO的值。 据说A应该将值传递为 B的一个参数,它应该通过它 作为C的论据。
这不能以可扩展的方式完成 然而,因为作者的作者 系统无法知道所有的东西 参数将是。想象一下 函数A和C是用户的一部分 扩展,而B是其中的一部分 标准体系。变量FOO确实如此 在标准体系中不存在;它 是扩展的一部分。使用 显式参数传递会 需要向B添加新参数, 这意味着重写B和一切 呼叫B.在最常见的情况下, B是编辑器命令调度程序 循环,从一个可怕的调用 地方数量。
更糟糕的是,C也必须通过 额外的论点。 B没有提到 按名称命名为C(B时不存在C. 写的)。它可能会找到一个 命令调度中指向C的指针 表。这意味着同一个电话 有时称C可能同样如此 好好调用任何编辑器命令 定义。所以所有的编辑 必须重写命令才能接受 并忽略其他参数。通过 现在,原始系统都没有 左!
就我个人而言,我认为如果Emacs-Lisp存在问题,那么它本身并不是动态范围,但它是默认的,并且不可能在不诉诸扩展的情况下实现词法范围。在CL中,可以使用动态和词法作用域,并且 - 除了顶级(由几个deflex实现处理)和全局声明的特殊变量 - 默认为词法作用域。在Clojure中,您也可以使用词法和动态范围。
再次引用RMS:
动态范围不一定是唯一提供的范围规则,只是有用 因为它可用。
答案 3 :(得分:11)
Peter Ajtai指出:
从emacs-24.1开始,您可以通过添加
在每个文件的基础上启用词法作用域;; -*- lexical-binding: t -*-
在你的elisp文件之上。
答案 4 :(得分:10)
首先,elisp具有单独的变量和函数绑定,因此动态范围的一些缺陷并不相关。
其次,您仍然可以使用setq
来设置变量,但是值集不会在它完成的动态范围退出后继续存在。从根本上说,这与词法范围不同,与动态范围的区别在于您调用的函数中的setq会影响函数调用后看到的值。
有lexical-let
,一个宏(基本上)模仿词汇绑定(我相信它是通过行走身体并将所有出现的词法变量变为gensymmed名称,最终取消内部符号来实现的),如果你绝对需要。
我会说“正常编写代码”。有时候,elisp的动态性质会咬你,但我发现在实践中这种情况很少令人惊讶。
以下是我对setq和动态绑定变量的说法示例(最近在附近的 scratch 缓冲区中进行了评估):
(let ((a nil))
(list (let ((a nil))
(setq a 'value)
a)
a))
(value nil)
答案 5 :(得分:8)
这里写的所有内容都是值得的。我想补充一点:了解 Common Lisp - 如果没有别的,请阅读它。与其他书籍一样,CLTL2很好地呈现了词汇和动态绑定。 Common Lisp将它们整合到一种语言中。
如果你在接触到Common Lisp之后“得到它”,那么对于Emacs Lisp来说,事情会更加清晰。 Emacs 24默认使用词法范围比默认版本更大,但Common Lisp的方法仍然更清晰,更清晰(恕我直言)。最后,由于RMS和其他人强调的原因,动态范围对Emacs Lisp很重要, 肯定是 。
所以我的建议是要了解 Common Lisp 如何处理这个问题。如果这是Lisp的主要心理模型,那就试着忘记Scheme吧 - 它将限制你,而不是帮助你理解Emacs Lisp中的范围,funargs等。与Common Lisp一样,Emacs Lisp“肮脏而低调”;它不是Scheme。
答案 6 :(得分:5)
当一段代码用于与其定义的范围不同的范围时,动态和词汇范围有不同的行为。实际上,有两种模式可以覆盖最麻烦的情况:
函数隐藏全局变量,然后调用另一个使用该全局变量的函数。
(defvar x 3)
(defun foo ()
x)
(defun bar (x)
(+ (foo) x))
(bar 0) ⇒ 0
这在Emacs中并不常见,因为局部变量往往具有短名称(通常是单字),而全局变量往往具有长名称(通常以packagename-
为前缀)。许多标准函数的名称很容易用作list
和point
等局部变量,但函数和变量存在于单独的名称空间中,本地函数不经常使用。
函数在一个词汇上下文中定义,并在此词汇上下文之外使用,因为它被传递给更高阶的函数。
(let ((cl-y 10))
(mapcar* (lambda (elt) (* cl-y elt)) '(1 2 3)))
⇒ (10 20 30)
(let ((cl-x 10))
(mapcar* (lambda (elt) (* cl-x elt)) '(1 2 3)))
⇑ (wrong-type-argument number-or-marker-p (1 2 3))
错误是由于cl-x
中使用mapcar*
作为变量名称(来自cl
包)。请注意,cl
包使用cl-
作为前缀,即使是高阶函数中的局部变量也是如此。这在实践中运行得相当好,只要您注意不要使用相同的变量作为全局名称和本地名称,并且您不需要编写递归的高阶函数。
P.S。 Emacs Lisp的年龄并不是它动态范围的唯一原因。没错,在那些日子里,lisps倾向于动态范围 - Scheme和Common Lisp还没有真正开始。但动态范围界定也是一种旨在动态扩展系统的语言的资产:它可以让您无需任何特别的努力即可连接到更多地方。凭借巨大的力量,你可以自己上吊绳索:你不小心碰到了一个你不知道的地方。
答案 7 :(得分:4)
其他答案擅长解释有关如何使用动态范围的技术细节,所以这是我的非技术建议:
就这样做
我已经修补了Emacs lisp超过15年,并且由于词汇/动态范围之间的差异,我不知道我曾经因为任何问题而被所困扰。
就个人而言,我没有发现需要关闭(我爱他们,只是不需要它们用于Emacs)。而且,我通常会尽量避免全局变量(范围界定是词汇还是动态)。
因此,我建议您跳入并编写适合您需求/愿望的自定义项,您可能不会遇到任何问题。
答案 8 :(得分:2)
我完全感受到你的痛苦。我发现emacs中缺乏词法绑定而不是很烦人 - 特别是不能使用词法闭包,这似乎是我想到的很多解决方案,来自更现代的语言。
虽然我没有任何关于解决之前的答案尚未涵盖的缺乏功能的建议,但我想指出一个名为`lexbind'的emacs分支的存在,实现词法绑定向后兼容的方式。根据我的经验,在某些情况下,词法闭包仍然有点小问题,但这个分支似乎是一种很有前途的方法。
答案 9 :(得分:2)
请不要。
Emacs-24允许您使用词法范围。跑吧
(setq lexical-binding t)
或添加
;; -*- lexical-binding: t -*-
在文件的开头。