13

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为普通宏: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:

这个解决方案有效,但感觉很笨拙,因为新的特殊形式不标准,不能正确突出显示,不能进入 under edebug,并且通常会引起注意。有没有更好的办法?


编辑

两个更智能(不一定是好的)解决方案的想法示例,允许代码继续使用标准表单来创建闭包:

  • 使用建议或编译器宏来lexical-let扩展至仅当仅分配给let在词法范围内的符号时。这个建议只会在 的字节编译期间被临时激活,因此对于 Emacs 的其余部分来说,它的含义保持不变。lexical-bindingslexical-letfoo.ellexical-let

  • 使用宏/code-walker 工具将let非前缀符号编译到lexical-let较旧的 Emacsen 下。这将再次仅适用于foo.el.

如果这些想法带有过度设计的味道,请不要惊慌:我不建议按原样使用它们。我对上述宏的替代方案感兴趣,其中包获得了更好的可移植使用闭包的好处,代价是加载/编译的一些额外复杂性。


编辑 2

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

如果有人对此兼容层有替代建议,或者上述想法的优雅实现,我鼓励他们回答。

4

3 回答 3

7

由于lexical-let和 lexical-binding 的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).

于 2012-09-18T03:22:25.240 回答
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))
于 2012-09-17T22:54:01.763 回答
-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)))

这个 shim 应该允许较新的 Emacs 读取较旧代码的 lexical-let 部分,以及以其他方式(允许较旧的 Emacs 读取较新代码的 let 部分)。

不过,那是 Common Lisp。有人愿意把它翻译成 Emacs 吗?

如果 lexical-let/let 被实现为特殊形式(不是宏),您可能会遇到麻烦。

此外,如果 let 在较旧的 Emacs 中定义,这可能会完全破坏前向兼容的情况。是吗?(我对 Emacs 知之甚少;它不是我选择的编辑器)。但无论如何,向后兼容的情况可能更重要。

于 2012-09-21T23:50:49.993 回答