如何使用Emacs Lisp动态范围?

时间:2010-09-24 10:10:12

标签: emacs lisp elisp scope

我之前学过Clojure,非常喜欢这门语言。我也喜欢Emacs,并用Emacs Lisp攻击了一些简单的东西。有一件事使我无法在精神上做任何与Elisp有关的事情。这是动态范围的概念。我只是害怕它,因为它对我来说太陌生,闻起来像半全局变量。

因此,对于变量声明,我不知道哪些事情是安全的,哪些是危险的。根据我的理解,使用setq设置的变量属于动态范围(是吗?)让变量怎么样?在某个地方我读过,让你可以做一些简单的词法范围,但在其他地方我读过,让vars也是动态范围的。

我最担心的是我的代码(使用setq或let)意外地从我调用的平台或第三方代码中破坏了一些变量,或者在调用之后我的局部变量意外地搞砸了。我怎么能避免这个?

我是否可以遵循一些简单的经验法则,确切地知道范围会发生什么,而不会被某种奇怪的,难以调试的方式所困扰?

10 个答案:

答案 0 :(得分:45)

这并不差。

主要问题可能出现在函数中的“自由变量”中。

(defun foo (a)
  (* a b))

在上面的函数中,a是一个局部变量。 b是一个自由变量。在具有动态绑定的系统(如Emacs Lisp)中,b将在运行时查找。现在有三种情况:

  1. b未定义 - >错误
  2. b是由当前动态范围中的某个函数调用绑定的局部变量 - >拿这个值
  3. b是一个全局变量 - >拿这个值
  4. 问题可能是:

    • 绑定值(全局或本地)被函数调用遮蔽,可能不需要
    • 未定义的变量未被遮蔽 - >访问错误
    • 全局变量未被遮蔽 - >获取可能不需要的全局值

    在带有编译器的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,您将获得许多类似这样的详细信息:

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

以下是一个例子:

(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-为前缀)。许多标准函数的名称很容易用作listpoint等局部变量,但函数和变量存在于单独的名称空间中,本地函数不经常使用。

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

    (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 -*-

在文件的开头。