【发布时间】:2012-09-09 12:07:31
【问题描述】:
Emacs 24 为局部变量添加了可选的词法绑定。我想在我的模块中使用这个功能,同时保持与 XEmacs 和以前的 Emacs 版本的兼容性。
在 Emacs 24 之前,获得闭包的最简单方法是使用在 cl-macs 中定义的 lexical-let 形式,它通过一些巧妙的宏技巧来模拟词法范围。虽然这在 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在lexical-bindings下扩展为let,但前提是lexical-let仅分配给无论如何在词法范围内的符号。这个建议只会在foo.el的字节编译期间被临时激活,因此lexical-let的含义对于Emacs 的其余部分保持不变。在旧版 Emacsen 下,使用宏/code-walker 工具将无前缀符号的
let编译为lexical-let。这将再次仅适用于foo.el的字节编译期间。
如果这些想法带有过度设计的味道,请不要惊慌:我不建议按原样使用它们。我对上述宏的替代方案感兴趣,其中包获得了更好的便携使用闭包的好处,但代价是加载/编译的一些额外复杂性。
编辑 2
由于没有人采取一种解决方案来允许模块继续使用let 或lexical-let 而不会在其余 Emacs 中破坏它们,我接受 Stefan 的回答,其中指出上述宏 是的方法。除此之外,答案通过使用bound-and-true-p 并为 edebug 和 lisp-indent 添加了一个优雅的声明来改进我的代码。
如果有人对此兼容层有替代建议,或者上述想法的优雅实现,我鼓励他们回答。
【问题讨论】:
-
如果我用普通的 lisp 编写这个宏包装器,我可能会在这里使用相同的基本方法。除了我会通过在顶层有一个单独的 defmacro bla-lexlet 来取消评估,然后有一个编译时 if 构造来确定将代码扩展为哪种形式(在这种情况下是 let 或 lexical-let )。我不会将此作为答案 b/c 这是一种常见的 lisp 方法,并且与您使用的基本方法相同。
-
好点,我现在修改了示例,将
if移动到宏扩展时间。但是,问题中指出的反对foo-lexlet的论点仍然成立。
标签: emacs lisp closures elisp lexical-scope