rwa*_*ace 4 lisp macros common-lisp
许多 Lisp 家族语言都有一些语法糖,比如加法或比较,允许两个以上的操作数,if可选地省略备用分支等。用宏实现这些会有一些话要说,会扩展(+ a b c)到(+ a (+ b c))等;这将使实际的运行时代码更清晰、更简单并且稍微快一点(因为每次添加一对数字时都不必运行检查额外参数的逻辑)。
但是,通常的宏扩展算法是“不断扩展最外层的形式,直到得到非宏结果”。所以这意味着例如+最好不要是一个扩展到 的宏+,即使是缩小版本,否则你会得到一个无限循环。
是否有任何现有的 Lisp 可以在宏扩展时解决这个问题?如果是这样,它是如何做到的?
小智 9
这是 Rainer 答案的附录:这个答案实际上只是给出了一些例子。
首先,编译算术运算之类的东西是一件麻烦事,因为你有一种特别的动机去尝试尽可能多地转化为机器理解的运算,如果不这样做,可能会导致数值密集型代码的速度大幅减慢。所以通常编译器有很多关于如何编译事物的知识,并且它也有很多自由:例如在 CL 中(+ a 2 b 3)可以被编译器变成
(+ 5 a b):编译器被允许重新排序和合并事物(但不是更改评估顺序:它可以变成(+ (f a) (g b))类似(let ((ta (f a)) (tb (g b))) (+ tb ta))但不能变成(+ (g b) (f a))) 的东西。
所以算术通常很神奇。但是看看如何使用宏来做到这一点以及为什么在 CL 中需要编译器宏仍然很有趣。
(注意:下面所有的宏都是我没有多想就写的:它们可能在语义上是错误的。)
所以,另外,在 CL 中。一个明显的技巧是拥有一个“primitive-two-arg”函数(大概编译器可以在良好的情况下内联到汇编中),然后让公共接口成为一个扩展到它的宏。
所以,这里是
(defun plus/2 (a b)
;; just link to the underlying CL arithmetic
(+ a b))
Run Code Online (Sandbox Code Playgroud)
然后你可以用显而易见的方式编写通用函数:
(defun plus/many (a &rest bcd)
(if (null bcd)
a
(reduce #'plus/2 bcd :initial-value a)))
Run Code Online (Sandbox Code Playgroud)
现在你可以编写公共接口,plus作为一个宏:
(defmacro plus (a &rest bcd)
(cond ((null bcd)
a)
((null (rest bcd))
`(plus/2 ,a ,(first bcd)))
(t
`(plus/2 (plus/2 ,a ,(first bcd))
(plus ,@(rest bcd))))))
Run Code Online (Sandbox Code Playgroud)
你可以看到
(plus a b)扩展为(plus/2 a b)'(plus a b c)扩展到(plus/2 (plus/2 a b) (plus c)),然后扩展到(plus/2 (plus/2 a b) c)。我们可以做得比这更好:
(defmacro plus (a &rest bcd)
(multiple-value-bind (numbers others) (loop for thing in (cons a bcd)
if (numberp thing)
collect thing into numbers
else collect thing into things
finally (return (values numbers things)))
(cond ((null others)
(reduce #'plus/2 numbers :initial-value 0))
((null (rest others))
`(plus/2 ,(reduce #'plus/2 numbers :initial-value 0)
,(first others)))
(t
`(plus/2 ,(reduce #'plus/2 numbers :initial-value 0)
,(reduce (lambda (x y)
`(plus/2 ,x ,y))
others))))))
Run Code Online (Sandbox Code Playgroud)
现在你可以扩展(plus 1 x y 2.0 3 z 4 a)到(plus/2 10.0 (plus/2 (plus/2 (plus/2 x y) z) a)),例如,我认为这对我来说还不错。
但这是无望的。这是无望的,因为如果我说会发生什么(apply #'plus ...)?Doom:plus必须是函数,不能是宏。
这就是编译器宏的用武之地。让我们重新开始,但这次函数(上面从未使用过)plus/many将是plus:
(defun plus/2 (a b)
;; just link to the underlying CL arithmetic
(+ a b))
(defun plus (a &rest bcd)
(if (null bcd)
a
(reduce #'plus/2 bcd :initial-value a)))
Run Code Online (Sandbox Code Playgroud)
现在我们可以为 编写一个编译器宏plus,这是一个可以被编译器使用的特殊宏:
函数或宏的编译器宏定义的存在表明编译器希望使用编译器宏的扩展而不是原始函数形式或宏形式。但是,实际上调用编译器宏函数或在调用编译器宏函数时使用结果扩展不需要任何语言处理器(编译器、评估器或其他代码遍历器)。– CLHS 3.2.2.1.3
(define-compiler-macro plus (a &rest bcd)
(multiple-value-bind (numbers others) (loop for thing in (cons a bcd)
if (numberp thing)
collect thing into numbers
else collect thing into things
finally (return (values numbers things)))
(cond ((null others)
(reduce #'plus/2 numbers :initial-value 0))
((null (rest others))
`(plus/2 ,(reduce #'plus/2 numbers :initial-value 0)
,(first others)))
(t
`(plus/2 ,(reduce #'plus/2 numbers :initial-value 0)
,(reduce (lambda (x y)
`(plus/2 ,x ,y))
others))))))
Run Code Online (Sandbox Code Playgroud)
请注意,此编译器宏的主体与plus上述 as 宏的第二个定义相同:它是相同的,因为对于此函数,没有宏想要拒绝扩展的情况。
您可以使用以下命令检查扩展compiler-macroexpand:
> (compiler-macroexpand '(plus 1 2 3 x 4 y 5.0 z))
(plus/2 15.0 (plus/2 (plus/2 x y) z))
t
Run Code Online (Sandbox Code Playgroud)
第二个值表示编译器宏没有拒绝扩展。和
> (apply #'plus '(1 2 3))
6
Run Code Online (Sandbox Code Playgroud)
所以这看起来不错。
与普通宏不同,像这样的宏可以拒绝扩展,它通过返回整个宏形式不变来实现。例如,这是上述宏的一个版本,它只处理非常简单的情况:
(define-compiler-macro plus (&whole form a &rest bcd)
(cond ((null bcd)
a)
((null (rest bcd))
`(plus/2 ,a ,(first bcd)))
(t ;cop out
form)))
Run Code Online (Sandbox Code Playgroud)
现在
> (compiler-macroexpand '(plus 1 2 3 x 4 y 5.0 z))
(plus 1 2 3 x 4 y 5.0 z)
nil
Run Code Online (Sandbox Code Playgroud)
但
> (compiler-macroexpand '(plus 1 2))
(plus/2 1 2)
t
Run Code Online (Sandbox Code Playgroud)
好的。