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

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

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

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

但 Emacs lisp 不支持这些。

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

那我该怎么做呢?

【问题讨论】:

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

标签: emacs closures elisp


【解决方案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

    【讨论】:

    【解决方案4】:
    【解决方案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) ))))
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多