Emacs中的词法范围:与旧版Emacsen的兼容性

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为普通的或者是普通的:letlexical-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 letlexical-bindings,lexical-let只能分配给词法范围的符号.这个建议只会在字节编译期间暂时激活foo.el,以便lexical-let其余Emacs 的含义保持不变.

  • 使用宏/代码 - walker工具将let未加前缀的符号编译到lexical-let较旧的Emacsen下.这将再次仅适用于字节编译期间foo.el.

如果这些想法有过度工程的味道,请不要惊慌:我不打算按原样使用它们.我对以上宏的替代方案感兴趣,其中包获得了更好的便携式闭包使用的好处,但是加载/编译的一些额外复杂性的代价.


编辑2

由于没有人提出一个解决方案,允许模块继续使用letlexical-let不打破其余的Emacs,我接受Stefan的回答,其中指出上述宏实现它的方法.除此之外,通过使用bound-and-true-p和添加edebug和lisp-indent的优雅声明,答案改进了我的代码.

如果有人为此兼容性层提供了替代提议,或者对上述想法的优雅实施,我鼓励他们回答.

Ste*_*fan 7

因为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).