1. 带参数的模板
反引用(quasiquotation)是普通引用(quotation)的带参数版本,
我们可以预留一些占位符,再具体指定某些值来填充它们,
因此,反引用更像一个模板(template)。
反引用的概念在Lisp很多方言中都有,例如Scheme和Common Lisp,
一般用来书写宏(macro)。
(define-macro (push expr var)
`(set! ,var (cons ,expr ,var)))
符号\
,创建了一个反引用表达式,与用符号
'创建的引用表达式非常相似。 反引用表达式中
,var表示我们要填充的是
var的值,而不是符号
var`。
expr
和var
的值,取决于某次具体的调用,假如(push a '(1 2))
,
则在这次调用中,expr => a
,var => '(1 2)
。
(其中,x => y
表示x
的值为y
`(set! ,var (cons ,expr ,var)) => (set! '(1 2) (cons a '(1 2)))
事实上,反引用是一个语法糖,
读取器(reader)会将反引用表达式展开成一段Lisp代码。例如,
`(set! ,var (cons ,expr ,var)) -> (list 'set! var (list 'cons expr var))
(其中,a -> b
表示a
展开为b
因此,反引用只是构建列表的一个便捷方法。
2. 嵌套
由于Lisp的宏定义程序,仍然是一个S表达式,
所以,我们就可以定义另一个宏来生成这个S表达式了。
这在其他语言中是很难办到的,因为它们引入了新的语法结构。
当我们遇到多个类似的宏定义时,
定义一个宏来展开成这些宏定义是理所当然的,
就不得不用反引用表达式来创建另一个反引用表达式,嵌套出现了。
例如,我们如下定义了两个宏,
(define-macro (catch var expr)
`(call/cc
(lambda (,var) ,expr)))
(define-macro (collect var expr)
`(call-with-new-collector
(lambda (,var) ,expr)))
它们非常类似,几乎一模一样,它们肯定某个抽象情况的两种实例,
于是,这引导我们去定义了如下的宏,
(define-macro (def-caller abbrev proc)
`(define-macro (,abbrev var expr)
`(,',proc
(lambda (,var) ,expr))))
而原来的两个宏定义可以用def-caller
来生成,
(def-caller catch call/cc)
-> (define-macro (catch var expr)
`(,'call/cc
(lambda (,var) ,expr)))
(def-caller collect call-with-new-collector)
-> (define-macro (collect var expr)
`(,'call-with-new-collector
(lambda (,var) ,expr)))
3. 求值规则
上一节我们看到,,',proc
被替换成了,'call/cc
和,'call-with-new-collector
,
可是,',
是怎样得到的呢?嵌套反引用表达式是怎样求值的呢?
我们可以这样推导,
(define-macro (catch var expr)
`(call/cc
(lambda (,var) ,expr)))
= (define-macro (catch var expr)
(list 'call/cc
(list 'lambda (list var) expr)))
然后,def-caller
的定义就可以改写为,
(define-macro (def-caller abbrev proc)
`(define-macro (,abbrev var expr)
`(,',proc
(lambda (,var) ,expr))))
= (define-macro (def-caller abbrev proc)
`(define-macro (,abbrev var expr)
(list ',proc
(list 'lambda (list var) expr))))
= (define-macro (def-caller abbrev proc)
`(define-macro (,abbrev var expr)
`(,',proc
(lambda (,var) ,expr))))
每次都这样推导是很麻烦的,
因此Lisp程序员们总结了嵌套反引用表达式的求值规则。
规则:
(1)查看每一个,
表达式。
如果该表达式的\
嵌套层数多于
,数目,则保持不动; 如果该表达式的
` 嵌套层数等于
,数目,则**去掉最右边一个**
,,并将该表达式换成它的值,其余部分保持不动。 **(其中**,如果表达式的
` 嵌套层数小于
,`数目,则该反引用表达式不合法
(2)去掉最外层的反引号
例如:
(def-caller catch call/cc)
,
abbrev => catch
,proc => call/cc
。
我们来看反引用表达式的求值过程,
`(define-macro (,abbrev var expr)
`(,',proc
(lambda (,var) ,expr)))
(1)查看每一个,
表达式,,abbrev
,,',proc
,,var
,,expr
。
比较各表达式的`嵌套层数与,
数目,
,abbrev
的\
嵌套1层,
,数目为1,去掉最右边的
,,表达式换为它的值,其余部分保持不动,结果为
catch。
,',proc的
` 嵌套2层,
,数目为2,去掉最右边的
,,表达式换为它的值,其余部分保持不动,结果为
,'call/cc。
,var的
` 嵌套2层,
,数目为1,保持不动。
,expr的
` 嵌套2层,
,`数目为1,保持不动。
(2)去掉最外层的反引号
(define-macro (catch var expr)
`(,'call/cc
(lambda (,var) ,expr)))
参考
Quasiquotation in Lisp