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 包,但它不依赖于该包。