[Lisp] 嵌套反引用

作者: 何幻 | 来源:发表于2016-07-24 13:02 被阅读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

相关文章

  • [Lisp] 嵌套反引用

    1. 带参数的模板 反引用(quasiquotation)是普通引用(quotation)的带参数版本,我们可以预...

  • [Emacs] Emacs之魂(八):反引用与嵌套反引用

    1. 反引用 上文我们介绍了如何使用defmacro定义宏, 我们定义了inc宏,(inc x)会被展开为(set...

  • Atom 编辑器的 Markdown书写学习(二)

    【5】文字引用 > 引用文字1 引用文字2 >> 嵌套引用文字3 >>> 嵌套引用文字4 表现效果 【6】文字强调...

  • Markdown 引用

    引用的多层嵌套 区块引用可以嵌套(例如:引用内的引用),只要根据层次加上不同数量的 > : 代码: 显示效果: 请...

  • swift 嵌套类型

    嵌套类型实践示例官方demo 引用嵌套类型

  • 结构体嵌套

    结构体嵌套 结构体嵌套时应逐级引用

  • 初学和使用 markdown

    h1 h1 h2 h3 h4 h5 h6 区块引用1.区块引用嵌套1层112.区块引用嵌套1层区块引用嵌套2层区块...

  • SCSS

    scss语法scss常见用法阮一峰 sass语法 变量 $ 定义 嵌套引用 多值变量 变量计算 定义 嵌套引用,特...

  • Markdown||引用

    今天我来学习引用。 语法: 效果: 引用二级嵌套三级嵌套 今天学习的引用比较简单,只需要在文字内容前加 > 即可,...

  • markdown语法 2022-06-19

    标题 段落 换行符 加粗或者斜体 引用 嵌套引用 for example: this a testanother ...

网友评论

    本文标题:[Lisp] 嵌套反引用

    本文链接:https://www.haomeiwen.com/subject/fnnwjttx.html