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

Emacs中的词法范围:与旧Emacsen的兼容性,emacs,lisp,closures,elisp,lexical-scope,Emacs,Lisp,Closures,Elisp,Lexical Scope,Emacs24为局部变量添加了可选的词法绑定。我希望在我的模块中使用此功能,同时保持与XEmacs和以前的Emacs版本的兼容性 在Emacs 24之前,获取闭包的最简单方法是使用cl macs中定义的lexicallet表单,它通过一些巧妙的宏技巧模拟了词法范围。虽然这在elisp程序员中从未流行过,但它确实起了作用,创建了真正有效的闭包,只要您记得将它们包装在词法let中,就像在下面的伪代码中一样: (defun foo-open-tag (tag data) "Maybe open T

Emacs24为局部变量添加了可选的词法绑定。我希望在我的模块中使用此功能,同时保持与XEmacs和以前的Emacs版本的兼容性

在Emacs 24之前,获取闭包的最简单方法是使用
cl macs
中定义的
lexicallet
表单,它通过一些巧妙的宏技巧模拟了词法范围。虽然这在elisp程序员中从未流行过,但它确实起了作用,创建了真正有效的闭包,只要您记得将它们包装在
词法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的支持的同时,使用新词汇绑定的最佳方式是什么?目前,我通过定义一个特定于包的宏来解决这个问题,该宏扩展为
词法let
或普通
let
,具体取决于
词法绑定是否绑定且为true:

(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
下进入,并且通常会引起注意。有更好的办法吗


编辑

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

  • 使用建议或编译器宏使
    词法let
    扩展到
    词法绑定下的
    let
    ,前提是
    词法let
    仅分配给词法范围内的符号。此建议仅在
    foo.el
    的字节编译过程中临时激活,以便
    词法let
    的含义在其余的Emacs中保持不变

  • 使用宏/代码遍历工具将非前缀符号的
    let
    编译为旧Emacsen下的
    词法let
    。这同样只适用于
    foo.el
    的字节编译过程

如果这些想法带有过度工程的味道,请不要惊慌:我不打算按原样使用它们。我对上述宏的替代方案很感兴趣,在这些替代方案中,包可以更好地移植闭包,但代价是加载/编译的复杂性增加了一些


编辑2

由于没有人提出一个解决方案,允许模块继续使用
let
lexical let
,而不破坏Emacs的其余部分,因此我接受Stefan的回答,他指出上面的宏就是实现这一点的方法。除此之外,通过使用
bound-and-true-p
并添加一个优雅的edebug和lisp缩进声明,我的代码得到了改进


如果有人对这个兼容层有一个替代方案,或者对上述想法有一个优雅的实现,我鼓励他们回答。

一个可能的解决方案是使用
defadvice
钩住
词法let
扩展。我写了以下建议,似乎效果不错。这也是
byte compile
aware

(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))

由于
词法let
和词法绑定的
let
的作用并不完全相同(更具体地说,
词法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)

词法let看起来与let具有相同的arglist格式,那么类似这样的内容呢:

(if (older-emacs-p)
  (setf (macro-function 'let) (macro-function 'lexical-let))
  (setf (macro-function 'lexical-let) (macro-function 'let)))
此垫片应允许较新的Emacs读取较旧代码的词法let部分,以及其他方式(允许较旧的Emacs读取较新代码的let部分)

这是常见的口齿不清。有人愿意把它翻译成Emacs吗

如果将词法let/let实现为一种特殊形式(而不是宏),您可能会遇到麻烦


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

如果我用common lisp编写这个宏包装器,我可能会在这里使用相同的基本方法。除此之外,我会通过在顶层使用一个defmacro-bla-lexlet来禁止eval,然后使用一个编译时if构造来确定将代码扩展到哪种形式(在本例中为let或lexical-let)。我不是把它作为答案b/c这是一种常见的lisp方法,也是您使用的基本方法。好的一点,我现在修改了示例,将
if
移到宏扩展时间。然而,问题中指出的反对
foo-lexlet
的论点仍然存在。您是作为
.emacs
中的用户使用此建议,还是作为包中的第三方包作者使用此建议?我不清楚激活此建议不会改变用户背后任何地方的
lexical let
行为。感谢
declare
提示,我将使用它。我会推迟接受你的回答一段时间,以鼓励其他想法。为了简化这一过程,我对问题进行了编辑,添加了此类解决方案可能采取的方向的示例。第二个
setf
似乎是多余的。您的意思是使用单个
psetf
来交换
let
lexical let
的宏函数吗?这看起来会改变Emacs会话剩余部分中
let
的含义,以便编译单个包。如果一个模块在没有警告的情况下这样做,用户将被严重破坏。