17.4 编译时求值

这些特性允许你编写在程序编译阶段执行的代码。

Macro: eval-and-compile body…

该形式标记 body 部分,使其在编译包含此代码的文件时执行, 且在运行该代码(无论是否已编译)时也会执行。

你也可以将 body 放入独立文件,再通过 require 引用该文件, 达到类似效果。当 body 内容较多时,推荐使用这种方式。 实际上 require 本身就等效于自动应用了 eval-and-compile, 对应的包会在编译和执行阶段均被加载。

autoload 同样等效于隐式的 eval-and-compile。 编译器在编译时会识别 autoload 定义, 因此调用这类函数不会产生「未定义」警告。

eval-and-compile 的大多数用法都具有较强的专业性:

若某个宏依赖辅助函数构建结果,且该宏既在包内部使用、也对外暴露, 则需通过 eval-and-compile 确保辅助函数在编译和运行阶段均可用;

若函数是通过编程方式定义的(例如使用 fset), eval-and-compile 可让定义逻辑在编译和运行阶段都执行, 从而使编译器能检查这些函数的调用(并抑制 “未定义(not known to be defined)” 警告)。

Macro: eval-when-compile body…

该形式标记 body 部分,使其仅在编译阶段执行, 而在加载编译后的程序时不执行。编译器对 body 求值的结果 会作为常量嵌入编译后的程序中。若直接加载源文件(而非编译后文件), 则 body 会按正常流程求值。

若某个常量需要通过计算生成,可使用 eval-when-compile 在编译阶段完成计算。例如:

(defvar gauss-schoolboy-problem
  (eval-when-compile (apply #'+ (number-sequence 1 100))))

若你使用其他包但仅需其中的宏(字节编译器会展开这些宏), 可通过 eval-when-compile 仅在编译阶段加载该包, 而运行阶段不加载。例如:

(eval-when-compile
  (require 'my-macro-package))

对于文件内本地定义、且仅在文件内部使用的宏和 defsubst 函数, 也适用此方式:编译文件时需要这些定义,但编译后的文件运行时 通常无需保留。例如:

(eval-when-compile
  (unless (fboundp 'some-new-thing)
    (defmacro some-new-thing ()
      (compatibility code))))

这种写法常用于仅作为兼容性回退的代码,适配不同版本的 Emacs。

Common Lisp 说明: 在顶层作用域中,eval-when-compile 等效于 Common Lisp 中的惯用写法 (eval-when (compile eval) …)。 在其他位置,Common Lisp 的 ‘#.’ 读取宏(解释执行时除外) 更接近 eval-when-compile 的行为。


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© 2025 Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike