Test your ELisp knowledge: (cl-defsubst f (x) (f x)) 为什么定义阶段就会死循环

模仿一下 Test your ELisp knowledge

等价于执行

(defmacro boo (x)
  `(boo ,x))
(defun foo (x)
  (boo x))

正解,zsbd

我是不是该反过来考考你,为什么用 defsubst 不会卡住?

cl-defsubst 由于会首先定义 compiler macro 而导致最后的 cl-defunmacroexpand-all 时会出现宏的无限展开,但 defsubst 仅在 byte-compile 生成字节码时起作用。

(所以无限循环会发生在调用 byte-compile 时。

1 个赞

你说的大部分都对,不过严格来说最后导致 black hole 的 compiler macroexpand 是由 defalias 来完成的,也就是 Emacs 为了支持 cl compiler macro 在解释器上开了洞。

2 个赞

有意思

找个时间看看去

这东西有点黑。

没记错的话,cl-defsubstcl-lib 的遗留产物,是利用 compiler macro 来工作的,正常来说不应该使用。

如果真的需要 compiler macro 重写的话,可以自己写 compiler macro 实现,或者用 define-inline

而且 cl-defsubst 的实现其实有漏洞,inline.el 里就举了这么一个例子

;; - cl-defsubst: only works by accident, since it has latent bugs in its
;;   handling of variables and scopes which could bite you at any time.
;;   (e.g. try (cl-defsubst my-test1 (x) (let ((y 5)) (+ x y)))
;;         and then M-: (macroexpand-all '(my-test1 y)) RET)

4 个赞

很早之前就看过你写的那篇 浅析Elisp中的compiler macro,一年前我写一个关于在 Emacs 中使用内联函数的总结的时候很有帮助。在测试 cl-defsubst 的时候,和你在帖子里描述的错误一致。那时候我用的是 Emacs 28.2。

不过这个 bug 在这个 commit 中被修复了: Faster and less wrong cl-defsubst inlining,现在 cl-defsubst 也能正常展开了。下面分别是我在 Emacs 29.2 和 Emacs 30.0.50 中得到的结果:

;; 29.2
(cl-defsubst my-test1 (x) (let ((y 5)) (+ x y)))
(macroexpand-all '(my-test1 y))
⇒ (let ((y 5)) (+ y y))
;; 30.0.50
(cl-defsubst my-test1 (x) (let ((y 5)) (+ x y)))
(macroexpand-all '(my-test1 y))
⇒ (let* ((x y)) (let ((y 5)) (+ x y)))
4 个赞