Emacs中的词法范围:与旧Emacsen的兼容性
Emacs24为局部变量添加了可选的词法绑定。我希望在我的模块中使用此功能,同时保持与XEmacs和以前的Emacs版本的兼容性 在Emacs 24之前,获取闭包的最简单方法是使用Emacs中的词法范围:与旧Emacsen的兼容性,emacs,lisp,closures,elisp,lexical-scope,Emacs,Lisp,Closures,Elisp,Lexical Scope,Emacs24为局部变量添加了可选的词法绑定。我希望在我的模块中使用此功能,同时保持与XEmacs和以前的Emacs版本的兼容性 在Emacs 24之前,获取闭包的最简单方法是使用cl macs中定义的lexicallet表单,它通过一些巧妙的宏技巧模拟了词法范围。虽然这在elisp程序员中从未流行过,但它确实起了作用,创建了真正有效的闭包,只要您记得将它们包装在词法let中,就像在下面的伪代码中一样: (defun foo-open-tag (tag data) "Maybe open T
cl macs
中定义的lexicallet
表单,它通过一些巧妙的宏技巧模拟了词法范围。虽然这在elisp程序员中从未流行过,但它确实起了作用,创建了真正有效的闭包,只要您记得将它们包装在词法let
中,就像在下面的伪代码中一样:
(defun foo-open-tag (tag data)
"Maybe open TAG and return a function that closes it."
... do the work here, initializing state ...
;; return a closure that explicitly captures internal state
(lexical-let ((var1 var1) (var2 var2) ...)
(lambda ()
... use the captured vars without exposing them to the caller ...
)))
问题是:在保留对Emacs 23和XEmacs的支持的同时,使用新词汇绑定的最佳方式是什么?目前,我通过定义一个特定于包的宏来解决这个问题,该宏扩展为词法let
或普通let
,具体取决于词法绑定是否绑定且为true:
(defmacro foo-lexlet (&rest letforms)
(if (and (boundp 'lexical-binding)
lexical-binding)
`(let ,@letforms)
`(lexical-let ,@letforms)))
(put 'foo-lexlet 'lisp-indent-function 1)
... at the end of file, turn on lexical binding if available:
;; Local Variables:
;; lexical-binding: t
;; End:
这个解决方案是可行的,但它感觉很笨拙,因为新的特殊表单是非标准的,不能正确突出显示,不能在edebug
下进入,并且通常会引起注意。有更好的办法吗
编辑
关于更智能(不一定是好的)解决方案的两个示例,允许代码继续使用标准表单创建闭包:
- 使用建议或编译器宏使
词法let
扩展到词法绑定下的let
,前提是词法let
仅分配给词法范围内的符号。此建议仅在foo.el
的字节编译过程中临时激活,以便词法let
的含义在其余的Emacs中保持不变
- 使用宏/代码遍历工具将非前缀符号的
let
编译为旧Emacsen下的词法let
。这同样只适用于foo.el
的字节编译过程
如果这些想法带有过度工程的味道,请不要惊慌:我不打算按原样使用它们。我对上述宏的替代方案很感兴趣,在这些替代方案中,包可以更好地移植闭包,但代价是加载/编译的复杂性增加了一些
编辑2
由于没有人提出一个解决方案,允许模块继续使用let
或lexical let
,而不破坏Emacs的其余部分,因此我接受Stefan的回答,他指出上面的宏就是实现这一点的方法。除此之外,通过使用bound-and-true-p
并添加一个优雅的edebug和lisp缩进声明,我的代码得到了改进
如果有人对这个兼容层有一个替代方案,或者对上述想法有一个优雅的实现,我鼓励他们回答。一个可能的解决方案是使用defadvice
钩住词法let
扩展。我写了以下建议,似乎效果不错。这也是byte compile
aware
(defadvice lexical-let (around use-let-if-possible (bindings &rest body) activate)
(if (and (>= emacs-major-version 24)
(boundp 'lexical-binding)
lexical-binding)
(setq ad-return-value `(let ,bindings . ,body))
ad-do-it))
由于词法let
和词法绑定的let
的作用并不完全相同(更具体地说,词法let
始终使用词法绑定,而let
根据var是否为defvar
'd使用动态绑定或词法绑定),我认为你的方法是最好的。您可以轻松地让Edebug加入其中,方法如下:
(defmacro foo-lexlet (&rest letforms)
(declare (indent 1) (debug let))
(if (bound-and-true-p lexical-binding)
`(let ,@letforms)
`(lexical-let ,@letforms)))
如果您不想依赖于declare
,可以使用(put'foo lexlet'edebug form spec'let)
词法let看起来与let具有相同的arglist格式,那么类似这样的内容呢:
(if (older-emacs-p)
(setf (macro-function 'let) (macro-function 'lexical-let))
(setf (macro-function 'lexical-let) (macro-function 'let)))
此垫片应允许较新的Emacs读取较旧代码的词法let部分,以及其他方式(允许较旧的Emacs读取较新代码的let部分)
这是常见的口齿不清。有人愿意把它翻译成Emacs吗
如果将词法let/let实现为一种特殊形式(而不是宏),您可能会遇到麻烦
此外,如果在旧的Emacs中定义了let,这可能会完全破坏前向兼容的情况。它是?(我对Emacs知之甚少;它不是我选择的编辑器)。但是向后兼容的情况可能更重要。如果我用common lisp编写这个宏包装器,我可能会在这里使用相同的基本方法。除此之外,我会通过在顶层使用一个defmacro-bla-lexlet来禁止eval,然后使用一个编译时if构造来确定将代码扩展到哪种形式(在本例中为let或lexical-let)。我不是把它作为答案b/c这是一种常见的lisp方法,也是您使用的基本方法。好的一点,我现在修改了示例,将if
移到宏扩展时间。然而,问题中指出的反对foo-lexlet
的论点仍然存在。您是作为.emacs
中的用户使用此建议,还是作为包中的第三方包作者使用此建议?我不清楚激活此建议不会改变用户背后任何地方的lexical let
行为。感谢declare
提示,我将使用它。我会推迟接受你的回答一段时间,以鼓励其他想法。为了简化这一过程,我对问题进行了编辑,添加了此类解决方案可能采取的方向的示例。第二个setf
似乎是多余的。您的意思是使用单个psetf
来交换let
和lexical let
的宏函数吗?这看起来会改变Emacs会话剩余部分中let
的含义,以便编译单个包。如果一个模块在没有警告的情况下这样做,用户将被严重破坏。