14.5.4 在展开过程中求值宏参数

如果宏定义本身会对宏参数表达式进行求值(例如调用 eval),就可能引发另一个问题(see 求值)。你必须考虑到:宏展开可能远早于代码实际运行,而调用者的上下文(宏展开式最终会在其中运行)此时还不可用。

此外,如果你的宏定义没有使用 lexical-binding(词法绑定),它的形参可能会遮蔽用户代码中的同名变量。在宏体内部,宏参数的绑定是该变量最内层的局部绑定,因此被求值表达式内部对该名称的任何引用,都会指向这个宏参数。示例如下:

(defmacro foo (a)
  (list 'setq (eval a) t))
(setq x 'b)
(foo x) → (setq b t)
     ⇒ t                  ; and b has been set.
;; but
(setq a 'c)
(foo a) → (setq a t)
     ⇒ t                  ; but this set a, not c.

用户代码中的变量是叫 a 还是 x,结果会完全不同,因为 a 会与宏的参数变量 a 发生冲突。

另外,上面的 (foo x) 在代码被编译时,展开结果会不一样,甚至直接报错。原因是:编译时 (foo x) 就会被展开,而 (setq x 'b) 要等到程序真正运行时才会执行。

要避免这类问题,不要在计算宏展开的过程中对参数表达式求值。正确的做法是:把表达式代入到宏展开式中,让它的值在执行展开后代码时才计算。本章的其他示例都是这么做的。

emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© 2025 Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike