为啥一个无关的局部绑定会引起编译警告?

我研究了下,在 emacs SE 的问题上更新回答了。结论是 Emacs 在这里报 warning 是符合预期的,29.1 引入的 closure 优化在这里没有改变应有的语义,而题目里的代码的确也是不符合规范的。

包括题主和可能 @include-yy 在內都犯了个错,

(let (_) ...) 的写法并不是一个没有记载的特殊用法,它的行为是和 (let (_a) ...) 甚至 (let (a) ...) (... 中没有引用 _a / a) 的行为是一样的。

(equal
 (byte-compile
  (lambda ()
    (let (_)
      (defvar abc-foo)
      (let (abc-foo)
        (setq abc-foo 1)))))
 (byte-compile
  (lambda ()
    (let (_a)
      (defvar abc-foo)
      (let (abc-foo)
        (setq abc-foo 1))))))
;; t

反而是 (let () ...) 有会被编译器优化成 (progn ...) 的特殊性,所以解釋器版本的 (let () ...) 为了统一也做成了 nop。

3 个赞

大神都用这么可爱的头像吗

2 个赞

是这样的,我在想这个问题的时候看了 let 的代码,如果 binding 什么也没有是不会有新的作用域的。只要有一个变量就行,_ 并不是什么关键字

我看了下 byte-compile-file 如下的文件

;;; -*- lexical-binding: t; -*-
(defvar -/a)
(let (_)
  (lambda ()
    -/a))

是不会报 warning 的, 此处的 (let (_) ... 也没有

ldb: nullify the (defvar ... declaration in global scope.

另外这代码看上去挺常规吧, 我把题目的代码改一下.


只有在 C-x C-e 下列表达式时, 会有 warning:

;;; -*- lexical-binding: t; -*-
(defvar -/a)
(byte-compile (let (_)
                (lambda ()
                  -/a)))

似乎 解释器 和 被 byte-compile 调用的 byte-compiler 之间有信息差. 不知道 @include-yy 怎么看.

字节编译文件时编译器会收集信息的, defvar 相当于声明有这个变量

一般来说用户用不到 byte-compile , byte-compile 主要也是对文件来进行

因为 defvar 在没有绑定变量时是不会产生任何副作用的,而是单纯作为一个声明表达式由 eval 或者编译器进行魔法处理。

整个文件进行编译时,编译器会搜索到 toplevel 的 defvar 声明。

手动执行 byte compile 时,编译函数看到的是已经由解释器处理过的 closure,报错不是因为搜索不到 defvar 声明,而是因为这个 closure 本身是有引用自由变量。

报错来源是执行

(byte-compile '(closure (t) nil foo-fpp))

而在给编译器足夠信息时是不会报错的。

(progn (defvar foo-fpp)
       (let (_)
         (lambda () foo-fpp)))
(closure (t) nil foo-fpp)

(byte-compile '(progn (defvar foo-fpp)
                      (let (_)
                        (lambda () foo-fpp))))
;;; no warning, produce same byte code

会有这种疑问就是对编程语言的实现细节没有概念,写代码就是纯凭 guts feeling 瞎湊的体现。


在 scratch buffer 里,

(defvar foo-fpp)

(lambda () foo-fpp)
(closure (foo-fpp t) nil foo-fpp)

(lambda () foo-fpp) ;; 把 defvar 注释后
(closure (t) nil foo-fpp)

本来还以为 29.1 的 closure 优化有 bug,结果只是虛惊一场忘删 defvar 了。

2 个赞