如何在 Emacs Lisp 中进行闭包?

Posted

技术标签:

【中文标题】如何在 Emacs Lisp 中进行闭包?【英文标题】:How do I do closures in Emacs Lisp? 【发布时间】:2010-10-10 06:04:21 【问题描述】:

我正在尝试动态创建一个返回一个常量值的函数。

javascript 和其他现代命令式语言中,我会使用闭包:

function id(a) 
    return function() return a;;

但 Emacs lisp 不支持这些。

我可以创建身份函数和部分函数应用程序的组合,但也不支持。

那我该怎么做呢?

【问题讨论】:

据我所知,JavaScript 实际上是相当实用的。 这取决于一个人的观点。对我来说,如果语言中的大多数代码都是命令式的,那么它就是命令式的。这里就是这种情况。 从版本 24 开始,Emacs 现在具有词法作用域。 【参考方案1】:
;; -*- lexical-binding:t -*-

(defun create-counter ()
  (let ((c 0))
    (lambda ()
      (setq c (+ c 1))
      c)))

(setq counter (create-counter))

(funcall counter) ; => 1
(funcall counter) ; => 2
(funcall counter) ; => 3 ...

【讨论】:

【参考方案2】:

Emacs 24 中的真实(非假)闭包。

虽然 Emacs 24 在变量 lexical-binding 的值为 t 时具有词法提取功能,但 defunc 特殊形式在词法绑定的上下文(至少在 Emacs 24.2.1 中没有。)这使得定义真正的(不是假的)闭包变得困难,但并非不可能。例如:

(let ((counter 0))
   (defun counting ()
    (setq counter (1+ counter))))

不会按预期工作,因为 defun 中的符号 counter 将绑定到该名称的全局变量(如果有的话),而不是词法变量在 let 中定义。当调用counting函数时,如果全局变量不存在,那么它显然会失败。但是,如果有这样一个全局变量,它会被更新,这可能不是预期的,并且可能是一个难以追踪的错误,因为该函数可能看起来工作正常。

如果您以这种方式使用 defun,字节编译器会发出警告,并且该问题可能会在 Emacs 的某些未来版本中得到解决,但在此之前可以使用以下宏:

(defmacro defun** (name args &rest body)
  "Define NAME as a function in a lexically bound context.

Like normal `defun', except that it works correctly in lexically
bound contexts.

\(fn NAME ARGLIST [DOCSTRING] BODY...)"
  (let ((bound-as-var (boundp  `,name)))
    (when (fboundp `,name)
      (message "Redefining function/macro: %s" `,name))
    (append
     `(progn
        (defvar ,name nil)
        (fset (quote ,name) (lambda (,@args) ,@body)))
     (if bound-as-var
         'nil
         `((makunbound `,name))))))

如果你定义counting如下:

(let ((counter 0))
  (defun** counting ()
    (setq counter (1+ counter))))

它将按预期工作并在每次调用时更新词法绑定变量count,同时返回新值。

CAVEAT:如果您尝试defun**一个与词法绑定变量之一同名的函数,该宏将无法正常工作。即,如果您执行以下操作:

(let ((dont-do-this 10))
  (defun** dont-do-this ()
    .........
    .........))

我无法想象有人真的会这样做,但值得一提。

注意:我已将宏命名为 defun** ,这样它就不会与 defun* 中的宏发生冲突>cl 包,但它不依赖于该包。

【讨论】:

你能举一个完整的例子吗?看来代码 (let ((counter 0)) (defun**counting () (setq counter (1+ counter)))) 没有按预期工作。 在 Emacs-24.3 中取消了使用 defun 定义闭包的限制(其中 defun 现在被定义为宏而不是特殊形式)。因此,由于 24.3 defun 像您的 defun** 宏一样工作(尽管没有损坏的 (defvar ,name nil) 并修复了各种其他小缺点,例如使用 fset 而不是 defalias 和处理所谓的“动态文档字符串”字节编译器中所需的更改)。【参考方案3】:

Emacs 24 具有词法绑定。

http://www.emacswiki.org/emacs/LexicalBinding

【讨论】:

另请参阅Variable Scoping 上的 GNU Emacs 手册中的部分【参考方案4】:

http://www.emacswiki.org/emacs/FakeClosures

【讨论】:

【参考方案5】:

Emacs lisp 只有动态作用域。有一个 lexical-let 宏通过一个相当可怕的 hack 来近似词法范围。

【讨论】:

当然,“相当可怕的黑客攻击”是在其他语言实现的掩护下发生的。【参考方案6】:

找到另一个使用 lexical-let 的解决方案

(defun foo (n) 
    (lexical-let ((n n)) #'(lambda() n)))

(funcall (foo 10)) ;; => 10

【讨论】:

【参考方案7】:

愚蠢的想法:怎么样:

(defun foo (x)
  `(lambda () ,x))

(funcall (foo 10))  ;; => 10

【讨论】:

当你想写这样的东西时,这会崩溃: (lexical-let ((a 0)) (cons (lambda () a) (lambda (new-a) (setf a new-a) ))))

以上是关于如何在 Emacs Lisp 中进行闭包?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Emacs Lisp 检查文件是不是存在?

emacs lisp,如何获得缓冲区主要模式?

emacs lisp

使用粘贴时如何跳转到emacs中的函数定义?

Common Lisp : Lexical varible , Dynamic varible ——作用域,生存期 ——environment : 绑定, 闭包与共享对象

Emacs怎么配置和安装?