如何使用Emacs Lispdynamic范围界定?

我以前学过Clojure,非常喜欢这种语言。 我也爱Emacs,并用Emacs Lisp破解了一些简单的东西。 但是有一件事情可以让我在精神上阻止Elisp做更多的事情。 这是dynamic范围的概念。 我只是害怕它,因为它对我来说太陌生了,闻起来像是半全球variables。

所以对于variables声明,我不知道哪些事情是安全的,哪些是危险的。 从我所了解的情况来看,使用setq设置的variables属于dynamic范围(是吗?)让variables怎么样? 我读过的一些地方,让你做简单的词法作用域,但是我读的其他地方,让vars也dynamic范围。

我最担心的是我的代码(使用setq或let)意外地打破了我调用的平台或第三方代码中的一些variables,或者在这样的调用之后,我的本地variables意外地混乱了。 我怎样才能避免这一点?

是否有一些简单的经验法则,我可以遵循,准确地知道范围发生了什么,而不会被一些奇怪的,难以debugging的方式咬伤?

这并不坏。

主要的问题可以出现在“自由variables”的function。

(defun foo (a) (* ab)) 

在上面的函数中, a是一个局部variables。 b是一个自由variables。 在一个像Emacs Lisp这样的具有dynamic绑定的系统中, b将在运行时被查找。 现在有三种情况:

  1. b没有定义 – >错误
  2. b是当前dynamic范围内某个函数调用所绑定的局部variables – >取该值
  3. b是一个全局variables – >取这个值

那么问题可以是:

  • 绑定值(全局或局部)被函数调用遮蔽,可能不需要
  • 一个未定义的variables不被遮蔽 – >访问错误
  • 一个全局variables是不会被映射的 – >select全局值,这可能是不需要的

在带有编译器的Lisp中,编译上述函数可能会产生一个警告,提示有一个自由variables。 通常,Common Lisp编译器会这样做。 解释器不会提供这个警告,只会在运行时看到效果。

build议

  • 确保你不使用自由variables
  • 确保全局variables有一个特殊的名字,以便在源代码中很容易识别,通常是*foo-var*

不要写

 (defun foo (ab) ... (setq c (* ab)) ; where c is a free variable ...) 

写:

 (defun foo (ab) ... (let ((c (* ab))) ...) ...) 

绑定你想要使用的所有variables,并且要确保它们没有绑定在别的地方。

基本上就是这样。

由于Emacs Lisp支持GNU Emacs版本24的词法绑定。 请参阅: 词汇绑定,GNU Emacs Lisp参考手册 。

是否有一些简单的经验法则,我可以遵循,准确地知道范围发生了什么,而不会被一些奇怪的,难以debugging的方式咬伤?

阅读Emacs Lisp Reference ,你会得到许多像这样的细节:

  • 特殊forms: setq [符号forms] …这种特殊forms是改变variables值最常用的方法。 每个SYMBOL都有一个新的值,这是评估相应FORM的结果。 符号的最本地现有绑定已更改

这里是一个例子:

 (defun foo () (setq tata "foo")) (defun bar (tata) (setq tata "bar")) (foo) (message tata) ===> "foo" (bar tata) (message tata) ===> "foo" 

除了Gilles最后一段的回答之外,下面是RMS如何在可扩展系统中支持dynamic范围 :

有些语言devise者认为应该避免dynamic绑定,而应该使用显式的parameter passing。 想象一下,函数A绑定variablesFOO,并调用函数B调用函数C,C使用FOO的值。 据说A应该把这个值作为parameter passing给B,它应该把它作为parameter passing给C.

但是,这不能在一个可扩展的系统中完成,因为系统的作者无法知道所有的参数是什么。 想象一下,函数A和C是用户扩展的一部分,而B是标准系统的一部分。 标准系统中不存在variablesFOO; 它是扩展的一部分。 要使用明确的parameter passing,需要向B添加一个新的参数,这意味着重写B和调用B的所有内容。在最常见的情况下,B是编辑器命令dispatcher循环,从很多地方调用。

更糟糕的是,C也必须通过一个额外的论证。 B不是用名字来指C(写B时C不存在)。 它可能在命令调度表中find一个指向C的指针。 这意味着有时调用C的调用同样可以调用任何编辑器的命令定义。 所以所有的编辑命令都必须重写,接受并忽略额外的参数。 到目前为止,原有的系统都没有了!

就我个人而言,我认为如果Emacs-Lisp出现问题,它本身不是dynamic的范围确定,但是它是默认的,并且不可能在不借助扩展的情况下实现词法范围。 在CL中,dynamic范围和词汇范围都可以使用,除了顶层(由几个deflex实现来解决)和全局声明的特殊variables外,缺省是词法范围。 在Clojure中,你也可以使用词法和dynamic范围。

再次引用RMS:

dynamic作用域不需要是唯一提供的作用域规则,只是有用的。

首先,elisp具有单独的variables和函数绑定,所以dynamic范围的一些缺陷是不相关的。

其次,你仍然可以使用setq来设置variables,但是这个值集合在dynamic范围的退出时并不存在。这不同于词法范围,与dynamic范围setq不同在你调用的函数中会影响函数调用后的值。

这里有一个lexical-let一个macros(实质上)模仿词汇的绑定(我相信这是通过行走身体,将所有出现的词汇variables转换为一个简化的名字,最终使符号不会中间化)。

我会说“写代码是正常的”。 有时候,elisp的dynamic性会咬你,但我发现在实践中,这是惊人的很less。

下面是关于setq和dynamic绑定variables(最近在附近的暂存缓冲区中进行评估)的说明的示例:

 (let ((a nil)) (list (let ((a nil)) (setq a 'value) a) a)) (value nil) 

正如Peter Ajtai所指出的那样:

由于emacs-24.1,你可以通过放在每个文件的基础上启用词法作用域

 ;; -*- lexical-binding: t -*- 

在你的elisp文件的顶部。

这里写的所有东西都是值得的。 我会添加这个:了解Common Lisp – 如果没有其他的东西,请阅读它。 与其他书籍一样,CLTL2提供了词汇和dynamic绑定。 Common Lisp将它们很好地集成在一个语言中。

如果您在接触Common Lisp后“得到它”,那么对于Emacs Lisp来说,事情会变得更加清晰。 Emacs 24在默认情况下使用的词法范围比老版本更大,但是Common Lisp的方法仍然更清晰更清晰(恕我直言)。 最后,dynamic范围对于Emacs Lisp来说是非常重要的,这是RMS和其他人所强调的。

所以我的build议是了解Common Lisp如何处理这个问题。 试着忘掉Scheme,如果这是你主要的Lisp心智模式 – 它会限制你不仅仅是帮助你理解Emacs Lisp中的范围,funargs等。 像Common Lisp一样,Emacs Lisp是“肮脏低调的”; 这不是计划。

当一段代码被用在与定义的代码不同的范围内时,dynamic和词法范围的行为具有不同的行为。在实践中,有两种模式覆盖了大多数麻烦的情况:

  • 函数会隐藏一个全局variables,然后调用另一个使用该全局variables的函数。

     (defvar x 3) (defun foo () x) (defun bar (x) (+ (foo) x)) (bar 0) ⇒ 0 

    这在Emacs中并不经常出现,因为局部variables往往有简短的名字(通常是单个单词),而全局variables往往有很长的名字(通常以packagename-为前缀)。 许多标准函数的名字很容易用作局部variables,如listpoint ,但函数和variables存在于不同的名称空间中,本地函数不常用。

  • 一个函数被定义在一个词汇上下文中,并在这个词汇上下文之外使用,因为它被传递给一个更高阶的函数。

     (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*的variables名(来自cl包)。 请注意, cl包使用cl-作为前缀,即使是在高阶函数中的局部variables也是如此。 这在实践中运行得相当好,只要注意不要使用同一个variables作为全局名称和局部名称,而且不需要编写recursion高阶函数。

PS Emacs Lisp的年龄不是dynamic范围的唯一原因。 诚然,在那些日子里,Lisp倾向于dynamic范围 – Scheme和Common Lisp还没有真正采取。 但dynamic范围界定也是针对扩展系统dynamic的一种语言资源:它可以让你在没有任何特别努力的情况下进入更多的地方。 大力的绳索可以让你自己挂上钩子:你不小心挂上了你不知道的地方。

其他答案很好的解释了如何使用dynamic范围的技术细节,所以这里是我的非技术性的build议:

去做就对了

我一直在修补Emacs lisp 15年以上,不知道由于词法/dynamic范围的差异,我曾经被任何问题咬过。

就我个人而言,我没有发现需要closures(我爱他们,只是不需要他们的Emacs)。 而且,通常我总是试图避免全局variables(无论是范围界定是词法还是dynamic的)。

所以我build议跳进去,写一些符合你需要/需求的定制,你不会有任何问题。

我完全感觉到你的痛苦。 我发现在emacs中缺乏词法绑定相当烦人 – 特别是不能使用词法closures,这似乎是我想到的很多来自更现代语言的解决scheme。

虽然我没有任何关于以前的答案还没有涵盖的缺乏特性的build议,但我想指出一个名为“lexbind”的emacs分支的存在,兼容的方式。 根据我的经验,在某些情况下,词法closures仍然是一个小问题,但是这个分支似乎是一个很有前途的方法。

只是不要。

Emacs-24让你使用词法范围。 赶紧跑

(setq lexical-binding t)

或添加

;; -*- lexical-binding: t -*-

在你的文件的开始。