Emacs 24为局部变量添加了可选的词法绑定。我想在我的模块中使用此功能,同时保持与XEmacs和以前的Emacs版本的兼容性。
在Emacs 24之前,获取闭包的最简单方法是使用lexical-let
中定义的cl-macs
形式,它使用一些聪明的宏技巧模拟词法范围。虽然这在elisp程序员中从未如此受欢迎,但只要您记得将它们包装在lexical-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的支持的同时,使用新词法绑定的最佳方法是什么?目前,我通过定义特定于包的宏来扩展到lexical-let
或普通let
,这取决于lexical-binding
是否绑定并且为真,我解决了这个问题:
(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
下进入,并且通常会引起注意。还有更好的方法吗?
修改
允许代码继续使用标准表单创建闭包的更智能(不一定是好)解决方案的两个想法示例:
使用建议或编译器宏将lexical-let
展开到let
下的lexical-bindings
iff lexical-let
仅分配到词法作用域的符号。这个建议只会在foo.el
的字节编译过程中暂时激活,因此lexical-let
的含义对其余的Emacs保持不变。
使用宏/代码漫步工具将let
个未加前缀的符号编译为旧版Emacsen下的lexical-let
。这将仅适用于foo.el
。
如果这些想法有过度工程的气味,请不要惊慌:我不打算按原样使用它们。我对以上宏的替代方案感兴趣,其中包获得了更好的可移植用途闭包的好处,但价格还有一些额外的加载/编译复杂性。
编辑2
由于没有人提出一个解决方案,允许模块继续使用let
或lexical-let
而不打破其余的Emacs,我接受Stefan的回答,其中指出上面的宏是的方法。除此之外,通过使用bound-and-true-p
并为edebug和lisp-indent添加一个优雅的声明,答案改进了我的代码。
如果某人有此兼容层的替代提案,或优雅实施上述提示,我建议他们回答。
答案 0 :(得分:7)
由于lexical-let
和词法绑定的let
不一样(更具体地说lexical-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)
。
答案 1 :(得分:2)
一种可能的解决方案是使用defadvice
来挂钩lexical-let
扩展。我写了以下建议,似乎工作正常。这也是byte-compile
意识到的。
(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))
答案 2 :(得分:-1)
Lexical-let看起来和let一样具有相同的arglist格式,那么这样的事情呢:
(if (older-emacs-p)
(setf (macro-function 'let) (macro-function 'lexical-let))
(setf (macro-function 'lexical-let) (macro-function 'let)))
这个垫片应该允许更新的Emacs读取旧代码的词汇部分,以及另一种方式(允许旧的Emacs读取更新代码的部分)。
这是Common Lisp。有人想把它翻译成Emacs吗?
如果将lexical-let / let作为特殊形式(不是宏)实现,则可能会遇到麻烦。
此外,如果在旧的Emacs中定义了let,这可能会完全破坏向前兼容的情况。是吗? (我对Emacs知之甚少;这不是我选择的编辑)。但无论如何,向后兼容的情况可能更为重要。