【问题标题】:Lexical scope in Emacs: compatibility with older EmacsenEmacs 中的词法作用域:与旧版 Emacsen 的兼容性
【发布时间】: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-letlexical-bindings 下扩展为let,但前提是lexical-let 仅分配给无论如何在词法范围内的符号。这个建议只会在foo.el 的字节编译期间被临时激活,因此lexical-let 的含义对于Emacs 的其余部分保持不变。

  • 在旧版 Emacsen 下,使用宏/code-walker 工具将无前缀符号的 let 编译为 lexical-let。这将再次仅适用于 foo.el 的字节编译期间。

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


编辑 2

由于没有人采取一种解决方案来允许模块继续使用letlexical-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


【解决方案1】:

一种可能的解决方案是使用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))

【讨论】:

  • 您是作为.emacs 中的用户还是作为第三方包作者在您的包中使用此建议?对我来说,激活此建议不会改变lexical-let 在用户背后的其他任何地方的行为并不明显。
【解决方案2】:

由于lexical-let 和词法绑定的let 不完全相同(更具体地说,lexical-let 总是使用词法绑定,而 let 使用动态绑定或词法绑定,具体取决于 var 是否为 @987654325 @'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)

【讨论】:

  • 感谢declare 提示,我会使用它。我将推迟接受您的答案一段时间,以鼓励其他想法。为了使这更容易,我编辑了问题以添加此类解决方案可能采用的方向的示例。
【解决方案3】:

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 知之甚少;它不是我选择的编辑器)。但无论如何,向后兼容的情况可能更重要。

【讨论】:

  • 第二个setf 似乎是多余的——你的意思是用一个psetf 来交换letlexical-let 的宏功能吗?为了编译单个包,这看起来会在 Emacs 会话的其余部分中更改 let 的含义。一个在没有警告用户的情况下这样做的模块将被严重破坏。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-27
  • 2022-06-24
  • 1970-01-01
  • 2021-12-24
相关资源
最近更新 更多