1. 反引用
上文我们介绍了如何使用defmacro定义宏,
(defmacro inc (var)
(list 'setq var (list '1+ var)))
我们定义了inc宏,(inc x)会被展开为(setq x (1+ x)),因此,
(defvar x 0)
(inc x)
x ; 1
宏做的是语法对象的变换操作,因此几乎每个宏最后都返回一个列表,
可是,类似上述inc宏那样,每次都使用list来创建列表,是一件麻烦的事情,
所以,Lisp提供了反引用(quasiquote/backquote),可以便捷的生成列表。
例如,以上inc宏使用反引用来生成列表,可以修改为,
(defmacro inc (var)
`(setq ,var (1+ ,var)))
可以看到,反引用``(setq ,var (1+ ,var)))与(inc x)的展开式(setq x (1+ x))非常相像, 我们只需要将反引号` 去掉,然后将反引用表达式中的逗号表达式,var,替换为var绑定的值x`即可。
2. 反引用表达式的求值规则
下面我们通过几个例子来说明反引用的使用方式,其中=>表示“求值为”。
求值规则:
(1)如果反引用表达式中不包含逗号,,那么它和引用表达式是一样的,
因此反引用通常被看做是一种特殊的引用(quote)
`(a list of (+ 2 3) elements)
=> (a list of (+ 2 3) elements)
(2)反引用表达式中的逗号表达式会被求值
`(a list of ,(+ 2 3) elements)
=> (a list of 5 elements)
(3)反引用表达式中的,@表达式,也会被求值,但是要求其结果必须是一个列表,
,@会去掉列表的括号,将列表中的元素放到,@表达式出现的位置
(defvar x '(2 3))
`(1 ,@x 4)
=> (1 2 3 4)
`(1 ,@(cdr '(1 2 3)) 4)
=> (1 2 3 4)
3. 生成宏定义的宏

以上,我们定义了宏inc,
宏调用(inc x),会被展开为(setq x (1+ x))。
在编写宏的时候,一个常用的思路是,
先考虑展开关系,即我们期望将A展开为B,再根据这个线索编写相应的宏。
那么,我们可否编写一个宏,让它展开成(defmacro ...)呢?
是可以的,这是一种展开为宏定义的宏,它可以作为defmacro来使用。
考虑展开关系,我们期望将(create-inc)展开为
(defmacro inc (var)
`(setq ,var (1+ ,var)))
于是,宏create-inc就应该被这样定义,
(defmacro create-inc ()
`(defmacro inc (var)
`(setq ,var (1+ ,var))))
我们来试验一下,
(create-inc) ; 定义了inc
(defvar x 0)
(inc x) ; 使用inc
x ; 1
我们还可以给create-inc加上参数。
考虑展开关系,我们将(create-inc-n y)展开为,
(defmacro inc-n (var)
`(setq ,var (+ y ,var)))
那么create-inc-n应该怎么定义呢?事实上,
(defmacro create-inc-n (num)
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
第一次看到,',num的时候,我非常惊讶,这到底是什么?
4. 嵌套反引用

嵌套反引用指的是,一个反引用表达式中嵌套出现了另一个反引用表达式。
在生成宏定义的宏中,嵌套反引用经常出现。
嵌套反引用表达式中,经常会出现类似,',num这样的表达式,
它不能被写成,num,也不能被写成,,num,下面我们进行仔细的分析。
(1),num为什么不正确
先看一下展开关系,我们期望将(create-inc-n y)展开为,
(defmacro inc-n (var)
`(setq ,var (+ y ,var)))
即,嵌套反引用表达式,应该按下述方式求值,
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ y ,var)))
其中,,var是不应该被求值的,因为这是内层反引用需要的,
如果我们将,',num写成,num,那么它就和,var一样不会被求值了,
`(defmacro inc-n (var)
`(setq ,var (+ ,num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,num ,var)))
这和我们期望的展开关系不同。
(2),,num为什么不正确
写成,,num在求值最外层反引用表达式的时候,确实会求值num的值,
但是,在求值内层反引用表达式的时候,这个值还会被再求值一次。
(create-inc-n y)将被展开为,
`(defmacro inc-n (var)
`(setq ,var (+ ,,num ,var)))
=> (defmacro inc-n (var)
`(setq ,var (+ ,y ,var)))
可是,在进行宏调用(create-inc-n y)的时候,我们不应该关心y的值是什么,
因为在宏展开阶段,y可能还没有值。
而且,该展开式和我们预期的展开结果也不相同。
(3),',num是怎么来的
综上分析,我们需要在外层反引用表达式被求值的时候,求值num,
而在内层反引用表达式被求值的时候,不再继续求值num的值,
因此,我们需要给num的值加上一个引用来“阻止”求值。
因此,(create-inc-n y)会被展开为,
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,'y ,var)))
而内层反引用表达式被求值的时候,,'y将求值为y。
所以,(inc-n x)将被展开为
`(setq ,var (+ ,'y ,var))
=> (setq x (+ y x))
和我们期望的展开结果相同。
5. 嵌套反引用的求值规则

在生成宏定义的宏中,经常会出现嵌套反引用,
如果我们定义了另一个宏other-macro来生成create-inc-n的定义,
(defmacro other-macro ()
`(defmacro create-inc-n (num)
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var)))))
那么,将出现三层嵌套反引用。
不过,不用担心,嵌套反引用也是有求值规则的,以下我们用两层嵌套反引用作为例子来说明。
求值规则:
(1)嵌套反引用被求值的时候,一次求值,只去掉一层反引用,内层反引用不受影响,
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,'y ,var)))
(2)嵌套反引用表达式中的逗号表达式,是否被求值,要根据情况来定,
如果最外层嵌套反引用总共有n层,那么一定不会出现包含大于n个逗号的表达式,
且包含逗号数目小于n的表达式不会被求值,只有逗号数目等于n的表达式才会被求值。
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,'y ,var)))
最外层嵌套反引用总共有n=2层,
,var表达式包含一个逗号,1<n,不会被求值,
,',num表达式包含两个逗号,2=n,会被求值。
(3)被求值的逗号表达式,其求值方式是,
去掉最右边的一个逗号,然后将表达式替换成它的值。
`(defmacro inc-n (var)
`(setq ,var (+ ,',num ,var))))
=> (defmacro inc-n (var)
`(setq ,var (+ ,'y ,var)))
,',num,去掉最右边的逗号,'num,然后将num替换成它的值y,
于是得到了,'y。
参考
GNU Emacs Lisp Reference Manual
ANSI Common Lisp
On Lisp
Let Over Lambda
