实现宏时,最难理解的是反引号、逗号、和@符号,这里不重复基本的知识,而是介绍最难得部分“在宏里面定义宏”。即调用一个宏时,它会定义一个新的宏。一些例子中,会在宏参数前加逗号、引号、和@的复杂组合,怎么理解这些组合呢?这里用实验的方法来研究它们的作用,而不管什么规则、原理。先枚举各种组合:
0 'x:引号
1 x:没有符合
2 ,x:逗号
3 ,,x:两个逗号
4 ',x:引号逗号
5 ,',x:逗号引号逗号
6 ',',x:引号逗号引号逗号
7 ,,@x:逗号逗号 @符
8 ,@,x:逗号 @符逗号
实验用的测试代码如下,写到一个文件中,然后load到CLisp环境中
(defmacro m0 (x)
`(defmacro g0 ()
`(print 'x)))
(defmacro m1 (x)
`(defmacro g1 ()
`(print x)))
(defmacro m2 (x)
`(defmacro g2 ()
`(print ,x)))
(defmacro m3 (x)
`(defmacro g3 ()
`(print ,,x)))
(defmacro m4 (x)
`(defmacro g4 ()
`(print ',x)))
(defmacro m5 (x)
`(defmacro g5 ()
`(print ,',x)))
(defmacro m6 (x)
`(defmacro g6 ()
`(print ',',x)))
(defmacro m7 (x)
`(defmacro g7 ()
`(print ,,@x)))
(defmacro m8 (x)
`(defmacro g8 ()
`(print ,@,x)))
对宏m0 ~ m6 ,执行 (macroexpand-1 '(m0 abc)) 看结果
m0 (DEFMACRO G0 NIL '(PRINT 'X)) ;
m1 (DEFMACRO G1 NIL '(PRINT X)) ;
m2 (DEFMACRO G2 NIL (LIST 'PRINT X)) ;
m3 (DEFMACRO G3 NIL (LIST 'PRINT ABC)) ;
m4 (DEFMACRO G4 NIL (LIST 'PRINT (LIST 'QUOTE X))) ;
m5 (DEFMACRO G5 NIL '(PRINT ABC)) ;
m6 (DEFMACRO G6 NIL '(PRINT 'ABC)) ;
看以看到,参数x前面有两个逗号的,x全部被换成abc;参数x前面只有一个逗号或没有逗号的,仍然是x。这是展开外层宏时的规律。
对宏m0 ~ m6 ,执行 (m0 abc) 生成宏g0 ~ g6,然后对宏g0 ~ g6执行(macroexpand-1 '(g0)) 看结果
g0 (PRINT 'X) ;
g1 (PRINT X) ;
g2 LET*: variable X has no value
g3 LET*: variable ABC has no value
g4 LET*: variable X has no value
g5 (PRINT ABC) ;
g6 (PRINT 'ABC) ;
展开g2、g3、g4时报错了,需要提供x和abc的值,说明展开内层宏时会做参数替换。提供x和abc的值后再看结果
(setq x 'x-value)
(setq abc 'abc-value)
g2 (PRINT X-VALUE) ;
g3 (PRINT ABC-VALUE) ;
g4 (PRINT 'X-VALUE) ;
再来执行宏g0 ~ g0,看打印的结果,其中g2和g3报错了,要去提供x-value和abc-value的值。实际上,从g0 ~ g6的展开结果就可以猜出下面结果。
g0 x
g1 x-value
g2 SYSTEM::READ-EVAL-PRINT: variable X-VALUE has no value
g3 SYSTEM::READ-EVAL-PRINT: variable ABC-VALUE has no value
g4 X-VALUE
g5 ABC-VALUE
g6 ABC
总结一下结果,x是宏的形参,abc是执行外层宏提供给x的实参。
0 'x :打印x
1 x :打印x-value
2 ,x :打印x-value-value
3 ,,x :打印abc-value-value
4 ',x :打印x-value
5 ,',x :打印abc-value
6 ',',x :打印abc
测试时将x作为print的参数,如果将x放在函数的位置,即
(defmacro m0 (x)
`(defmacro g0 ()
(符号组合x ...)))
则变成
0 'x :非法,不能执行('x ...),因为不允许将'x作为函数名
1 x :执行内层宏时执行函数x
2 ,x :展开内层宏时执行函数x,执行内层宏时执行函数x-value
3 ,,x :展开内层宏时执行函数abc,执行内层宏时执行函数abc-value
4 ',x :展开内层宏时执行函数x
5 ,',x :执行内层宏时执行函数abc
6 ',',x :非法,不能执行('abc ...)
实际使用一下上面的分析结果,实现On-Lisp书上的缩略语的例子。大致样子如下,还没有确定参数long的前缀
(defmacro abbrev (short long)
`(defmacro ,short (&rest args)
`(long ,@args)))
期望的结果是,执行(abbrev df defun)后可以用(df ...)作为(defun ...)的缩略语。首先,参数long必须被替换成defun,因此前面必须有两个逗号,可以选3、5、6,排除0、1、2、4。前面已经分析6会变成('defun ...),不能执行,因此排除6,只剩下3和5。我们想直接执行defun,而不是将defun看成指向另一个函数的变量(执行defun指向的函数),因此排除3,选择5。即产生long的前缀是,',(逗号引号逗号)。
分析到这里,忽然觉得测试代码不够符合实际。在展开g2和g4时,报错要去提供x的值,我们弄了一个全局变量x,这不符合实际,应该以宏参数的形式提供x的值,即给宏g2和g4增加一个参数x。另外,总结结果时,总说“执行函数”导致
理解困难。为了对比,我们给g0 ~ g6都增加参数x,重新分析。修改后的测试代码如下:
(defmacro m0 (x)
`(defmacro g0 (x)
`(print 'x)))
对宏m0 ~ m6 ,执行 (m0 abc) 生成宏g0 ~ g6,然后对宏g0 ~ g6执行(macroexpand-1 '(g0 xyz)) 看结果
g0 (PRINT 'X) ;
g1 (PRINT X) ;
g2 (PRINT XYZ) ;
g3 (PRINT ABC-VALUE) ;
g4 (PRINT 'XYZ) ;
g5 (PRINT ABC) ;
g6 (PRINT 'ABC) ;
再来看一下宏abbrev,执行(abbrev df defun)生成宏df,执行(df ...)期望其等效于(defun ...)。和前面进行的测试进行对比,x变成long,abc变成defun,defun直接出现在最后的结果中,和g5对应上了。现在有了一个简单易行的方法,来确定两个反引号中各参数的前缀。假设用abc作为实参调用外层宏,用xyz作为实参调用内层宏,abc的值为abc-value,期望放参数的地方最终被替换成什么呢,拿你的期望和上面的测试结果对比,找对应上的。