Lisp Quote 和Backquote分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiaojianpitt/article/details/7747230
对于Lisp初学者来说,最不容易理解的就是Lisp强大的宏。在宏定义中你看到的最多的字符可能是就是引用和反引用。引用和反引用构成了Lisp的基石,所以我先就引用和反引用做一个介绍。宏就留在以后介绍了。

  • 引用 (quote)
引用Quote其实是Lisp 25个特殊操作符之一,它接受一个单一表达式作为其参数,并且简单的返回它。不经过求值过程。例如下面表达式(quote (+ 2 3)) 求值的话得到是列表 (+ 2 3),而不是5. Quote是读取宏(reader macro)的一个例子。 读取宏可以修改读取器用来将文本转化为Lisp对象的语法。

Quote 的等价形式是‘ ,‘是Quote的语法糖。两个形式是完全等价了。读取器将’翻译为quote的等价形式。

例如: (defunmess-with (number string)
  '(value-of-number (1+ number) something-with-string (length string)))

运行之后结果如下:
Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER (1+ NUMBER) SOMETHING-WITH-STRING (LENGTH STRING))
但是我们实际的意思是想要 number的值和 String的长度组成的一个列表。上面的写法是不能实现我们的要求的。看下面的这个定义:
(defun mess-with (number string)
  (list 'value-of-number (1+ number) 'something-with-string (length string)))

执行结果如下:

Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER 21 SOMETHING-WITH-STRING 3)

反引用和引用是等价的,下面是上面函数定义的反引用版本:
(defun mess-with (number string )
  `(value-of-string ,(+ 1 number ) something-with-string ,(length string )) )
下面是另一个例子:
Lisp> (quote spiffy-symbol)
SPIFFY-SYMBOL

Lisp> 'spiffy-symbol ; '是quote的等价形式,和上一个表达式相同
SPIFFY-SYMBOL
如果我们不使用引用,那么结果如下:
Lisp> spiffy-symbol
Unbound variable: SPIFFY-SYMBOL
   [Condition of type UNBOUND-VARIABLE]

Restarts:
 0: [CONTINUE] Retry getting the value of SPIFFY-SYMBOL.
Unbound variable: SPIFFY-SYMBOL
   [Condition of type UNBOUND-VARIABLE]

Restarts:
 0: [CONTINUE] Retry getting the value of SPIFFY-SYMBOL.
因为没有spiffy-symbol。
所以quote 和反引用(和逗号)和list 函数等是你创造列表的工具。 列表并不是简单的值的序列,其实可以看做一个轻量级的数据结构(不一定是结构)。
  


 进深阅读:Ron Garret's The Idiot's Guide to Common Lisp Packages  
 

  • 反引用(backquote)

反引用 (backquote) 是引用(quote) 的特别版本,它可以用来创建 Lisp 表达式的模板。反引用最常见的用途之一是用在宏定义里。

反引用字符  得名的原因是:它和通常的引号  相似,只不过方向相反。当单独把反引用作为表达式前

缀的时候,它的行为和引号一样:

‘(a b c) 等价于’(a b c) .

只有在反引用和逗号 , ,以及comma-at “,@  一同出现时才变得有用。如果说反引用创建了一个模板,那

么逗号就在反引用中创建了一个 slot。一个反引用列表等价于将其元素引用起来,调用一次list。也就

是,

‘(a b c) 等价于(list ’a ’b ’c) .

在反引用的作用域里,逗号要求 Lisp:“把引用关掉”。当逗号出现在列表元素前面时,它的效果就相当于取

消引用,让 Lisp 把那个元素按原样放在那里。所以

‘(a ,b c ,d) 等价于(list ’a b ’c d) .

插入到结果列表里的不再是符号b,取而代之的是它的值。无论逗号在嵌套列表里的层次有多深,它都仍

然有效,

> (setq a 1 b 2 c 3)

3>

‘(a ,b c)

(A 2 C)

> ‘(a (,b c))

(A (2 C))

而且它们也可以出现在引用的列表里,或者引用的子列表里:

> ‘(a b ,c (’,(+ a b c)) (+ a b) ’c ’((,a ’b)))

(A B 3 (’6) (+ A B) ’C ’((1 ’B)))

一个逗号能抵消一个反引用的效果,所以逗号在数量上必须和反引用匹配。如果某个操作符出现在逗号的

外层,或者出现在包含逗号的那个表达式的外层,那么我们说该操作符包围了这个逗号。例如在‘(,a ,(b

‘,c)) 中,最后一个逗号就被前一个逗号和两个反引号所包围。通行的规则是:一个被 n 个逗号包围的逗

号必须被至少 n + 1 个反引号所包围。很明显,由此可知:逗号不能出现在反引用的表达式的外面。只要

遵守上述规则,就可以嵌套使用反引用和逗号。下面的任何一个表达式如果输入到 toplevel 下都将造成错

误:

,x ‘(a ,,b c) ‘(a ,(b ,c) d) ‘(,,‘a)

  • 嵌套的反引用(nested backquote)
嵌套的反引用是Lisp宏的难点,正如《On Lisp》所说的:
“为了定义一个定义宏的宏,我们通常会要用到嵌套的反引用。嵌套反引用的难以理解是出了名的。尽管最
终我们会对那些常见的情况了如指掌,但你不能指望随便挑一个反引用表达式,都能看一眼,就能立即说出
它可以产生什么。这不能归罪于Lisp。就像一个复杂的积分,没人能看一眼就得出积分的结果,但是我们
不能因为这个就把问题归咎于积分的表示方法。道理是一样的。难点在于问题本身,而非表示问题的方法。”
stackoverflow有一个问答对Lisp的嵌套反引用解释的很透彻,我们下面来分析一下:
先来说一下嵌套反引用的解析规则:
CLTS2里面说:
如果反引号是嵌套的话,最内层的反引用形式(这是说的是最内层的逗号,其实是最外层的反引号)应该最先被展开。这意味着如果某个表达式有几个连续的逗号的话,最左边的那个逗号属于最里面的反引号。
R5RS Scheme语言规范关于反引号这么定义:
准引用可以嵌套。

举例分析:

``(a ,,(+ 1 2) ,(+ 3 4))
第一次求值得到如下表达式:
`(A ,3 ,(+ 3 4))
解析:1,左边反引用首先被展开(第一个反引用),所以(+ 1 2)被求值因为匹配逗号(第二个逗号)。
      2,另一个表达式,(+ 3 4)因为没有足够的逗号所以未求值。
      3,只有一个反引用被展开,因为反引用不会递归的展开。
第二次求值(展开所有的逗号)
为了在展开所有的反引用,我们采用如下表达式:
(eval ``(a ,,(+ 1 2) ,(+ 3 4)))

所用的反引用被展开,我们带到下面的求值结果:

(A 3 7)
 解析: 其实就是对第一次求值的结果继续求值,就可以得到上面的结果。

<<On Lisp>>一书上说,反引用的嵌套一般发生在定义宏的宏上面。下面是书中的一个例子:定义一个宏的简称的的宏。
因为一些CL的名字相当的长,比如destructuring-bind 和multiple-value-bind,所以我们可以定义宏来减少我们输入的字符
(defmacro dbind (&rest args)
   `(destructruing-bind ,@args))
(defmacro mvbind (&rest args)
   `(multiple-value-bind ,@args))
就可以了。我们可以看到dbind和mvbind是何等的相似。对于Lisp来说,宏是抽象和消除重复的好方法,那么我们为什么不再定义一个宏来
消除重复呢?假设我们想要得到一个abbrev宏,它允许我们使用(abbrev mvbind mutiple-value-bind)来定义缩写mvbind。下面是这个宏的定义:
(defmacro abbrev (short long)
  `(defmacro ,short (&rest args)
     `(,',long ,@args)))
卧槽,(,',XXXX),到这一步,我相信初学Lisper肯定凌乱了。其实我何尝不是呢。下面让我们一步一步分析这个宏定义是怎么来的。
我们可以从它的展开式开始,我们最终要一个如下的展开式:
(defmacro mvbind (&rest args)
`(multiple-value-bind ,@args))
我们如果先把multiple-value-bind从反引用中拉出来的话,推到就容易一点,得到如下等价的定义
(defmacro mvbind (&rest args)
(let ( (name 'multiple-value-bind ))
`(,name ,@args) ) )
现在我们将这个展开式转化为一个模板。我们把反引用放到前面,然后将可变的表达式变为一个变量
`(defmacro ,short (& rest args)
(let (( name ',long ))
`(,name ,@args) ) )
最后一步,我们把name 从内层反引用中消除,得到abbrev的宏的主体:
`(defmacro ,short (&rest args)
`(,',long ,@args) ) )
下面我们来正向分析,来展开abbrev宏,例如(abbrev mvbind mutiple-value-bind)
第一步:
首先展开最内层的反引用,和第一个逗号,得到结果
`(DEFMACRO ,SHORT (&REST ARGS) (LIST* ',LONG ARGS))
英文:

Backquote

The backquote introduces a template of a data structure to be built. For example, writing

 `(cond ((numberp ,x) ,@y) (t (print ,x) ,@y))
is roughly equivalent to writing
 (list 'cond 
       (cons (list 'numberp x) y) 
       (list* 't (list 'print x) y))
Where a comma occurs in the template, the expression following the comma is to be evaluated to produce an object to be inserted at that point. Assume b has the value 3, for example, then evaluating the form denoted by `(a b ,b ,(+ b 1) b) produces the result (a b 3 4 b).

If a comma is immediately followed by an at-sign, then the form following the at-sign is evaluated to produce a list of objects. These objects are then ``spliced'' into place in the template. For example, if x has the value (a b c), then

 `(x ,x ,@x foo ,(cadr x) bar ,(cdr x) baz ,@(cdr x))
=>  (x (a b c) a b c foo b bar (b c) baz b c)
The backquote syntax can be summarized formally as follows.
* `basic is the same as 'basic, that is, (quote basic), for any expression basic that is not a list or a general vector.


* `,form is the same as form, for any form, provided that the representation of form does not begin with at-sign or dot. (A similar caveat holds for all occurrences of a form after a comma.)


* `,@form has undefined consequences.


* `(x1 x2 x3 ... xn . atom) may be interpreted to mean


 (append [ x1] [ x2] [ x3] ... [ xn] (quote atom))
where the brackets are used to indicate a transformation of an xj as follows:
-- [form] is interpreted as (list `form), which contains a backquoted form that must then be further interpreted.


-- [,form] is interpreted as (list form).


-- [,@form] is interpreted as form.


* `(x1 x2 x3 ... xn) may be interpreted to mean the same as the backquoted form `(x1 x2 x3 ... xn . nil), thereby reducing it to the previous case.


* `(x1 x2 x3 ... xn . ,form) may be interpreted to mean


 (append [ x1] [ x2] [ x3] ... [ xn] form)
where the brackets indicate a transformation of an xj as described above.
* `(x1 x2 x3 ... xn . ,@form) has undefined consequences.


* `#(x1 x2 x3 ... xn) may be interpreted to mean (apply #'vector `(x1 x2 x3 ... xn)).


Anywhere ``,@'' may be used, the syntax ``,.'' may be used instead to indicate that it is permissible to operate destructively on the list structure produced by the form following the ``,.'' (in effect, to use nconc instead of append).

If the backquote syntax is nested, the innermost backquoted form should be expanded first. This means that if several commas occur in a row, the leftmost one belongs to the innermost backquote.

An implementation is free to interpret a backquoted form F1 as any form F2 that, when evaluated, will produce a result that is the same under equal as the result implied by the above definition, provided that the side-effect behavior of the substitute form F2 is also consistent with the description given above. The constructed copy of the template might or might not share list structure with the template itself. As an example, the above definition implies that

 `((,a b) ,c ,@d)
will be interpreted as if it were
 (append (list (append (list a) (list 'b) 'nil)) (list c) d 'nil)
but it could also be legitimately interpreted to mean any of the following:
 (append (list (append (list a) (list 'b))) (list c) d)
 (append (list (append (list a) '(b))) (list c) d)
 (list* (cons a '(b)) c d)
 (list* (cons a (list 'b)) c d)
 (append (list (cons a '(b))) (list c) d)
 (list* (cons a '(b)) c (copy-list d))
Nested Backquote

This is what the Common Lisp HyperSpec says about nested backticks:

If the backquote syntax is nested, the innermost backquoted form should be expanded first. This means that if several commas occur in a row, the leftmost one belongs to the innermost backquote.

The R5RS Scheme spec also includes these details about backticks:

Quasiquote forms may be nested. Substitutions are made only for unquoted components appearing at the same nesting level as the outermost backquote. The nesting level increases by one inside each successive quasiquotation, and decreases by one inside each unquotation.

Also keep in mind that only one backtick gets collapsed per evaluation, just like a regular quote, it's not recursive.

Rules in action

To see how these three details interact, let's expand your example a bit. This expression...

``(a ,,(+ 1 2) ,(+ 3 4))

Gets evaluated to this (in SBCL notation):

`(A ,3 ,(+ 3 4))
  1. The left backtick got collapsed, so it the (+ 1 2) got escaped by the matching comma (the 2nd comma, according to the HyperSpec).
  2. On the other hand, the (+ 3 4) didn't have enough commas to get expanded (which is what R5RS mentions).
  3. Only one backtick got collapsed, because backticks don't get recursively expanded.

Expanding both commas

To get rid of the other backtick, another level of evaluation is needed:

(eval ``(a ,,(+ 1 2) ,(+ 3 4)))

Both backticks are gone, and we're left with a plain list:

(A 3 7)

参考:
http://stackoverflow.com/questions/7549550/using-two-backquotes-and-commas-common-lisp
进一步阅读:

Nested Backquotes considered harmful






没有更多推荐了,返回首页

私密
私密原因:
请选择设置私密原因
  • 广告
  • 抄袭
  • 版权
  • 政治
  • 色情
  • 无意义
  • 其他
其他原因:
120
出错啦
系统繁忙,请稍后再试

关闭