如何在 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 中进行闭包?的主要内容,如果未能解决你的问题,请参考以下文章
Common Lisp : Lexical varible , Dynamic varible ——作用域,生存期 ——environment : 绑定, 闭包与共享对象