Lispemacs lisp语言介绍

[Lisp] 嵌套反引用

2016-07-24  本文已影响112人  何幻

1. 带参数的模板

反引用(quasiquotation)是普通引用(quotation)的带参数版本,
我们可以预留一些占位符,再具体指定某些值来填充它们,
因此,反引用更像一个模板(template)。

反引用的概念在Lisp很多方言中都有,例如Scheme和Common Lisp,
一般用来书写宏(macro)。

(define-macro (push expr var)
    `(set! ,var (cons ,expr ,var)))

符号\ ,创建了一个反引用表达式,与用符号'创建的引用表达式非常相似。 反引用表达式中,var表示我们要填充的是var的值,而不是符号var`。

exprvar的值,取决于某次具体的调用,假如(push a '(1 2))
则在这次调用中,expr => avar => '(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 => catchproc => 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

上一篇下一篇

猜你喜欢

热点阅读