42

我正在编写一个 emacs 主模式,它使用缓冲区局部变量来存储一些状态:

(defun foo-mode ()
  "My nice major mode"
  (interactive)
  (kill-all-local-variables)
  (setq mode-name "foo")
  (setq major-mode 'foo-mode)
  (set (make-local-variable 'foo-state) "bar"))

(defun foo-change-state ()
  (setq foo-state "baz"))

这工作得很好,并且具有在任何不使用我的主要模式的缓冲区中,foo-state变量没有绑定的属性(在我看来这是一件好事,因为它避免了符号表的混乱)。

但是,对这样一段代码进行字节编译会产生以下警告:

Warning: assignment to free variable `foo-state'

using消除了警告,但具有现在无处不在defvar的副作用,这在我看来是不可取的。foo-state

有没有办法摆脱警告,同时仍然不绑定每个缓冲区中的特定于模式的变量?或者当我认为这些变量不应该全局声明时我错了?

4

2 回答 2

44

做你想做的事情的官方方法是(defvar foo-state). 注意没有第二个参数。另请注意,这样的声明仅适用于找到它的文件(或找到它的范围,如果它在函数内使用)。

于 2012-09-15T02:24:14.077 回答
39

用 声明变量defvar。没有其他方法可以删除警告,这确实被认为是一种很好的做法。

您保持符号表整洁的意图是值得的,但您实际上并没有这样做。我认为您误解了 Emacs Lisp 中变量绑定的语义,因为您似乎相信通过不声明它将foo-state在任何不使用foo-mode. 事实并非如此

在 Emacs Lisp 中,名称(又名符号)是全局的。一旦foo-state第一次被评估,运行时就会为它创建一个新的符号对象foo-state并将其放入全局符号表(又名obarray)中。foo-state没有本地符号表,因此在哪里评估以及如何评估都无关紧要,在任何地方foo-state引用相同的符号对象(请参阅创建符号)。

每个符号对象都由组件(也称为单元格)组成,其中之一是变量单元格(请参阅符号组件)。 setq修改系统的当前绑定,在没有词法绑定的顶层,这有效地改变了符号对象的变量单元格,从而改变了变量的全局值。setq同样,在哪里评估并不重要。实际上,如果有些bar-mode评估(setq foo-state "bar")foo-state也必然会“酒吧”,foo-mode反之亦然。

(defvar)因此, over (setq)it的唯一效果是记录使用符号作为全局变量的意图,因此告诉其他人不要修改此变量,除非有意操作 of 的行为foo-mode。您可以将文档附加到变量,并标记为在缓冲区中定义(C-h v foo-state将提供跳转到定义的链接)。

由于 Emacs Lisp 缺少名称空间,并且默认情况下是动态范围的,因此文档对于避免模块之间的冲突至关重要。如果我写了一个bar-mode使用你的foo-mode我可能会不小心绑定到foo-state,调用foo-change-state然后看到我的模式行为不端,因为变量被无意覆盖。声明foo-state不会使这成为不可能,但它至少可以让我捕捉到错误,因为C-h v foo-state会揭示这个变量被另一个模式使用,所以我最好不要使用它,除非我真的打算操纵那个模式。

最后一句话:在所有上述文本中,“模式”都可以替换为 Emacs Lisp 文件。 modes在符号方面没有什么特别的。以上所有内容也适用于不声明模式但仅包含一堆函数的 Emacs Lisp。

于 2012-09-14T21:53:00.417 回答