emacs lisp中词法绑定和defvar之间奇怪的交互

时间:2013-07-01 08:53:51

标签: emacs lisp elisp lexical-scope dynamic-scope

以下emacs lisp文件是关于当Alice在其init文件中使用词法绑定的局部变量foo时会发生什么,而Bob将foo定义为defvar的全局特殊变量他的init文件和Alice将Bob的init文件代码的一部分借给了她自己的init文件,不知道foo会变得特别。

;; -*- lexical-binding: t; -*-
;; Alice init file

;; Alice defining alice-multiplier
(defun alice-multiplier-1 (foo)
  (lambda (n) (* n foo)))
(defun alice-multiplier-2 (num)
  (let ((foo num))
    (lambda (n) (* n foo))))

;; Alice using alice-multiplier
(print
 (list
  :R1 (mapcar (alice-multiplier-1 10) (list 1 2 3))
  :R2 (mapcar (alice-multiplier-2 10) (list 1 2 3))))

;; from Bob's code
;; ...    
(defvar foo 1000)
;; ...

;; Alice using alice-multiplier
(print
 (list
  :R3 (mapcar (alice-multiplier-1 10) (list 1 2 3))
  :R4 (mapcar (alice-multiplier-2 10) (list 1 2 3))))

输出:

(:R1 (10 20 30) :R2 (10 20 30))

(:R3 (10 20 30) :R4 (1000 2000 3000))

结果R1和R2正如我所料。结果R4与defvar文档一致,但除非她读取Bob的代码,否则它可能会让Alice感到惊讶。

  1. 我发现R3令人惊讶。 R3为什么这样?

  2. 说到R4,爱丽丝可以做些什么来保护她foo不被其他人转为特别?例如,foo可能是她在init文件或其中一个emacs包中使用的词法局部变量,而(defvar foo "something")可能位于她碰巧使用的某些包中,或foo可能是未来版本的Emacs引入的新特殊变量名称之一。有没有Alice可以在她的文件中写入对Emacs说“在这个文件中,foo应该总是词汇,即使外部的某些代码碰巧使用同名的特殊变量”?

2 个答案:

答案 0 :(得分:4)

发生了什么

从“理论”(Scheme / Common Lisp)的角度来看,一旦启用词法绑定,出于所有实际目的,alice-multiplier-1alice-multiplier-2 相同 。他们的行为有任何差异是Emacs Lisp中的一个错误,应该这样报告。

<强>编译

如果您将代码(即2 defun;; -*- lexical-binding: t; -*-行)放入文件emacs-list-byte-compile-and-load中,那么您可以通过评估这4种表单来测试我的声明:

(disassemble 'alice-multiplier-1)
(disassemble 'alice-multiplier-2)
(disassemble (alice-multiplier-1 10))
(disassemble (alice-multiplier-2 10))

你会看到3和4是相同的,1和2相差一条指令(应该向Emacs维护者报告错误,但不影响行为)。

请注意,所有反汇编都没有提及foo,这意味着defvar影响他们的行为。

一切都很好!

<强>被解释的

事实上,你看到的行为是不正确的; defvar之后的正确结果

(:R1 (10000 20000 30000) :R2 (10000 20000 30000))

report this to the emacs maintainers

<强>不同的???

是的,defvar (并且应该!)会影响解释代码的行为,(并且不应该!)影响编译的代码的行为。

你应该做什么

除了通过在foo前加上“您的”符号前,我们无法“保护”您的special被其他人宣布为alice-

但是,如果使用alice-multiplier-1定义对文件进行字节编译,则编译后的文件甚至不包含foo,因此foo的未来声明不会影响您。

答案 1 :(得分:1)

关于第二个问题,据我所知,没有。但人们仍然可以解决。这是采用两种命名约定:我称之为黄色和绿色。

黄色命名惯例

所有特殊变量都必须带有黄色名称。黄色名称是包含至少一个连字符的名称。例如,hello-worldga-na-da是黄色名称。官方的Emacs Lisp手册和字节编译器鼓励这种约定。

绿色命名惯例

绿色名称是非黄色的名称。例如,helloworldganada是绿色名称。

所有词汇非局部/自由变量必须具有绿色名称。什么是非局部变量? alice-multiplier-2中匿名函数的正文提到三个名称nfoonum。在这三个中,只有n在函数体(匿名函数)中有一个声明。从匿名函数的角度来看,另外两个是非本地的。它们是非局部变量。

只要爱丽丝和鲍勃坚持两个命名惯例,一切都很好。即使他们现在不这样做,这两个人很可能最终会在没有相互沟通的情况下通过这些阶段自己融入这些惯例:

  1. Alice和Bob坚持没有任何约定。

  2. 由于手册和字节编译器鼓励使用黄色命名约定,因此Alice和Bob都开始遵循黄色惯例。

  3. Alice采用绿色约定,至少保护她的代码免受其他人的黄色特殊变量的影响。

  4. Alice和Bob坚持两种惯例。

  5. 有关可能发生碰撞的精确条件,请参阅Invasion of special variables