如何理解 magit 源码中的 (cl-eval-when (load eval) ...) ?

这些语言又不会像 Emacs Lisp 那样分别有 S-exp interpreter 和 bytecode interpreter 各自为政(比如闭包变量的优化问题),大部分有 S-exp interpreter 的都是 bootstrap 用一下就丢了。或者说根本问题是 Emacs Lisp 编译后和编译前的语义不完全相同。且 cl-eval-when 加剧了这种撕裂,但并没有带来实际的好处。

byte compile 和 eval-when 没关系。

假设有一个 CL 编译器完全没有编译,所谓的编译就是做 macroexpand 到最底层的 primitive(这样的 CL 实现是符合标准的,其实也是 SICL 的早期实现)

在这个实现完成后,把 eval 改成编译 primitive sexp compiler 的,只要这个编译器实现正确,对这个实现的行为是不影响的。

macro 就是 macro,和 eval 是否采用编译器是无关的。

progn 在 CL 里面是宏定义里也能生效的,emacs 的实现的确和 CL 不同,实际上不是 eval 和 compiler 分开的问题,而是 emacs 没把宏展开和 bytecomp 正确分开的问题,很多 special form 依赖于 bytecomp 的行为。

所以 Emacs 就是这个问题 :sweat_smile: 无解

直接的作用是阻止 require 在编译时 eval。默认行为 require 是 (eval compile load) 的。

至于为啥要阻止编译时加在这个包,有没有更好办法,我就不知道了。

感谢两个小伙伴的耐心回复和热烈讨论。 不一致的根源在于忽略了源码的第一个判断条件

	  (if (and (macroexp-compiling-p)
		   (not cl--not-toplevel) (not (boundp 'for-effect))) ;Horrible kludge.

从源码层面看eval和compile, load是两条分支线下的结果。如果第一个条件不满足,会直接返回(cons 'progn body)。 需要把下文

  1. 顶层形式
  2. (cl-eval-when (compile) body ...) 在编译阶段, body 在顶层形式编译时求值;
  3. (cl-eval-when (load) body ...) 在加载阶段, body 在顶层形式编译后求值;
  4. (cl-eval-when (eval) body ...) 等同于 progn

中的第4条修正为(cl-eval-when (eval) body ...) 在解释阶段等同于progn

重新梳理一下:

  1. 顶层形式
  2. (cl-eval-when (compile) body ...) 在编译阶段, body 在顶层形式编译时求值;
  3. (cl-eval-when (load) body ...) 在加载阶段, body 在顶层形式编译后求值;
  4. (cl-eval-when (eval) body ...) 在解释阶段等同于 progn
  5. (cl-eval-when (load eval) body ...) 在任意上下文中都等同于progn

由上面5条引出的新问题是

  1. progn应该如何理解?其作为顶层形式具有什么作用?
  2. 在关于magit的源码实验中,是否保留(cl-eval-when (load eval) body...)对速度无影响,即是否阻止require在编译时eval没有意义,那么这句源码存在的意义是什么?

用来给 macro 返回多个表达式用。

有意义,可以减少编译用时。

感谢您的耐心解答哈。

From: Jonas Bernoulli <[email protected]>
Subject: Re: cl-eval-when -- A workaround for recursive require?
To: Stefan Monnier <[email protected]>, Zhu Zihao <[email protected]>
Cc: [email protected]
Date: Sun, 01 May 2022 20:36:15 +0200 (5 minutes, 39 seconds ago)
Flags: seen
Maildir: /all_but_last/Inbox

Stefan Monnier <[email protected]> writes:

> Zhu Zihao [2022-04-27 18:52:07] wrote:
>> A breif summary: magit.el use a `cl-eval-when` block with load time and
>> eval time only evaluation to require its sub-components, while each
>> sub-component use `(require 'magit)` to use procedure in different
>> sub-components. This hack seems to be a hack to avoid recursive require.
>
> This kind of setup is quite common, but the resulting cyclic
> dependencies tend to be a nightmare.

Yeah, I got a bit lazy there, but I am working on it now.

     Jonas

我觉得可以终结本帖了

2 个赞