摆脱"引用自由变量"字节编译警告

ffe*_*tte 39 emacs elisp

我正在写一个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"))
Run Code Online (Sandbox Code Playgroud)

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

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

Warning: assignment to free variable `foo-state'
Run Code Online (Sandbox Code Playgroud)

使用defvar摆脱警告,但foo-state现在到处都有副作用,这在我看来是不可取的.

有没有办法摆脱警告,同时仍然没有绑定每个缓冲区中特定于模式的变量?或者当我认为这些变量不应该在全球范围内宣布时,我错了吗?

Ste*_*fan 42

做你想要的官方方式是(defvar foo-state).注意没有第二个参数.另请注意,此类声明仅适用于找到它的文件(如果在函数内部使用,则适用于找到它的范围).


lun*_*orn 36

用变量声明变量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)(setq)它是文档使用符号作为全局变量,所以告诉别人,除非行为的操作不修改这个变量的意图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,它不声明模式,但只包含一堆函数.

  • "foo-state"不会在其他缓冲区中解除绑定 - 只是试试海报的例子.当obarray是全局的时,缓冲区本地符号的值在每个缓冲区交换机上改变,这可以包括对未绑定状态的改变.如果你在`foo-mode`缓冲区中按`M-:foo-state RET`,你会得到``bar``,而如果你在另一个缓冲区中做同样的事情,你会得到一个错误.虽然这在elisp中不是惯用的,但无论全局符号表如何,都可以获得它. (2认同)