我研究了下,在 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 个赞
是这样的,我在想这个问题的时候看了 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 个赞