我该如何在Emacs Lisp中使用闭包?

32

我试图创建一个即时生成的函数,它将返回一个常量值。

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

function id(a) {
    return function() {return a;};
}

但是Emacs lisp不支持这些。

我可以创建一个标识函数和部分函数应用的混合体,但也不受支持。

那么我该怎么做呢?


据我所知,JavaScript 实际上是相当函数式的。 - Svante
这取决于个人的观点。对我来说,如果语言中大部分的代码是命令式的,那它就是命令式的。而这正是现在的情况。 - vava
从版本24开始,Emacs现在具有词法作用域。 - Ryan C. Thompson
7个回答

35

我发现使用 lexical-let 可以找到另一种解决方案。

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

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

13

Emacs 24 中真正的闭包(非伪装)。

尽管 Emacs 24 在变量lexical-binding的值为t时具有词法作用域,但是在词法绑定的上下文中,defun特殊形式不能正常工作(至少在 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))))))

如果您将“计数”定义为以下内容:
(let ((counter 0))
  (defun** counting ()
    (setq counter (1+ counter))))

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

注意:如果您尝试使用与词法绑定变量之一同名的函数进行 defun ** ,则宏将无法正常工作。 也就是说,如果您执行以下操作:

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

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

注意:我将宏命名为defun** 以避免与cl包中的宏defun冲突,但它并不依赖于该包。


你能给一个完整的例子吗?代码似乎不像预期那样工作 (let ((counter 0))(defun** counting ()(setq counter (1+ counter))))。 - toolchainX
3
在Emacs-24.3中,关于使用“defun”定义闭包的限制被取消了(其中“defun”现在被定义为宏而不是特殊形式)。因此,从24.3版本开始,“defun”就像您的“defun**”宏一样工作(尽管没有损坏的“(defvar,name nil)”和修复各种其他较小缺陷,例如使用“defalias”而不是“fset”以及处理所谓的“动态文档字符串”,这需要字节编译器进行更改)。 - Stefan

10

愚蠢的想法:怎么样:

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

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

3
当你想要编写类似以下的代码时,这种方法就会失效:(lexical-let ((a 0)) (cons (lambda () a) (lambda (new-a) (setf a new-a)))) - jrockway

7

Emacs lisp只有动态作用域。有一个lexical-let宏,通过一种相当可怕的技巧近似词法作用域。


10
当然,“相当可怕的黑客”是其他语言实现中底层操作的内容。 - jrockway

4

请参考GNU Emacs手册中关于变量作用域的部分。 - dat

3
;; -*- 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 ...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接