Emacs:defun 或 defmacro 主体中的代码不能引用周围的词法变量?

Jis*_*Yoo 5 lisp emacs elisp lexical-scope emacs24

2013 年 5 月更新:从 GNU Emacs 24.3.1 开始,(let .. (defun..)) 字节编译很好,没有警告,并且字节编译代码与未编译代码的工作方式相同。只是不要忘记将文件变量添加lexical-binding: t到要字节编译的文件中。现在不需要这个问题末尾的解决方法。


词法绑定 - Emacs Lisp 手册中有这样一段:

请注意,像 symbol-value、boundp 和 set 这样的函数仅检索或修改变量的动态绑定(即其符号值单元格的内容)。此外,defun 或 defmacro 主体中的代码不能引用周围的词法变量。

我不确定我是否理解了第二句话的意思。在以下应在词法绑定模式下运行的代码中, defun 主体中的代码成功引用了 name 的词法绑定值n

(let ((n 0))
  (defun my-counter ()
    (incf n)))

(my-counter) ;; 1
(my-counter) ;; 2
Run Code Online (Sandbox Code Playgroud)

这句话只是说 (let .. (defun ..)) 是一种不好的做法吗?


解决方法

;; -*- lexical-binding: t -*-

;; a way to define the counter function without byte-compile error or warning

(defvar my--counter-func
  (let ((n 0))
    (lambda ()
      (setq n (1+ n)))))

(defun my-counter ()
  (funcall my--counter-func))

;; another way to define the counter function, again without byte-compile error or warning

(fset 'my-another-counter
      (let ((n 0))
        (lambda ()
          (setq n (1+ n)))))
Run Code Online (Sandbox Code Playgroud)

这是测试上述代码的代码:

;; run:
;; emacs -q --load path-to-the-el-file-of-this-code.el

(load "path-to-file-defining-my-counter.elc") ;; loading the ELC file to test if byte-compiled code runs as expected.

(print (my-counter)) ;; 1
(print (my-counter)) ;; 2

(print (my-another-counter)) ;; 1
(print (my-another-counter)) ;; 2
Run Code Online (Sandbox Code Playgroud)

dki*_*kim 1

该代码至少在 Emacs 24.1.1 中不能很好地进行字节编译。我将以下代码保存在文件中foo.el,它使用setq来代替incf以避免cl库造成任何可能的影响:

;; -*- lexical-binding: t -*-

(let ((n 0))
  (defun my-counter ()
    (setq n (1+ n))))
Run Code Online (Sandbox Code Playgroud)

当我尝试对其进行字节编译(M-x byte-compile-filefoo.el)时,我收到以下警告消息:

foo.el:3:1:Warning: Function my-counter will ignore its context (n)
foo.el:3:1:Warning: Unused lexical variable `n'
foo.el:5:11:Warning: reference to free variable `n'
foo.el:5:17:Warning: assignment to free variable `n'
Run Code Online (Sandbox Code Playgroud)

所有消息都表明构造主体中的代码defun无法引用周围的词法变量n无法像手册声明那样

实际上,当我加载字节编译代码(M-x load-filefoo.elc)并评估(my-counter)表单时,我得到了以下错误:

Debugger entered--Lisp error: (void-variable n)
  ...
Run Code Online (Sandbox Code Playgroud)

不幸的是,我不确定为什么代码在以源代码形式评估时似乎可以工作。