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:
这个解决方案有效,但感觉很笨拙,因为新的特殊形式不标准,不能正确突出显示,不能进入 under edebug
,并且通常会引起注意。有没有更好的办法?
编辑
两个更智能(不一定是好的)解决方案的想法示例,允许代码继续使用标准表单来创建闭包:
使用建议或编译器宏来
lexical-let
扩展至仅当仅分配给let
在词法范围内的符号时。这个建议只会在 的字节编译期间被临时激活,因此对于 Emacs 的其余部分来说,它的含义保持不变。lexical-bindings
lexical-let
foo.el
lexical-let
使用宏/code-walker 工具将
let
非前缀符号编译到lexical-let
较旧的 Emacsen 下。这将再次仅适用于foo.el
.
如果这些想法带有过度设计的味道,请不要惊慌:我不建议按原样使用它们。我对上述宏的替代方案感兴趣,其中包获得了更好的可移植使用闭包的好处,代价是加载/编译的一些额外复杂性。
编辑 2
由于没有人采取一种解决方案来允许模块在 Emacs 的其余部分继续使用let
或lexical-let
不破坏它们,我接受 Stefan 的回答,其中指出上述宏是实现它的方法。除此之外,答案通过使用bound-and-true-p
和添加 edebug 和 lisp-indent 的优雅声明来改进我的代码。
如果有人对此兼容层有替代建议,或者上述想法的优雅实现,我鼓励他们回答。