use*_*342 13 lisp emacs closures elisp lexical-scope
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 ...
)))
Run Code Online (Sandbox Code Playgroud)
问题是:在保留对Emacs 23和XEmacs的支持的同时,使用新词法绑定的最佳方法是什么?目前我通过定义一个特定于包的宏来解决它,它根据是否为bound和true 扩展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:
Run Code Online (Sandbox Code Playgroud)
这个解决方案有效,但是它感觉很笨,因为新的特殊形式是非标准的,没有正确地突出,不能被踩到下面edebug
,并且通常会引起注意.有没有更好的办法?
编辑
两个智能(不一定是好)解决方案的想法示例,允许代码继续使用标准表单来创建闭包:
使用建议或编译器宏来lexical-let
扩展到iff let
下lexical-bindings
,lexical-let
只能分配给词法范围的符号.这个建议只会在字节编译期间暂时激活foo.el
,以便lexical-let
其余Emacs 的含义保持不变.
使用宏/代码 - walker工具将let
未加前缀的符号编译到lexical-let
较旧的Emacsen下.这将再次仅适用于字节编译期间foo.el
.
如果这些想法有过度工程的味道,请不要惊慌:我不打算按原样使用它们.我对以上宏的替代方案感兴趣,其中包获得了更好的便携式闭包使用的好处,但是加载/编译的一些额外复杂性的代价.
编辑2
由于没有人提出一个解决方案,允许模块继续使用let
或lexical-let
不打破其余的Emacs,我接受Stefan的回答,其中指出上述宏是实现它的方法.除此之外,通过使用bound-and-true-p
和添加edebug和lisp-indent的优雅声明,答案改进了我的代码.
如果有人为此兼容性层提供了替代提议,或者对上述想法的优雅实施,我鼓励他们回答.
因为lexical-let
和lexical-binding let
不一样(更具体地说,lexical-let
总是使用词法绑定,而let
使用动态绑定或词法绑定取决于var是否为defvar
'),我认为你的方法和它一样好.您可以轻松地让Edebug进入它,因为:
(defmacro foo-lexlet (&rest letforms)
(declare (indent 1) (debug let))
(if (bound-and-true-p lexical-binding)
`(let ,@letforms)
`(lexical-let ,@letforms)))
Run Code Online (Sandbox Code Playgroud)
如果您不想依赖declare
,可以使用(put 'foo-lexlet 'edebug-form-spec 'let)
.