Lisp Quote 和Backquote分析

babyyellow發表於2012-09-07

原文地址 : http://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   
 
  • 反引用(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)
 解析: 其實就是對第一次求值的結果繼續求值,就可以得到上面的結果。

<>一書上說,反引用的巢狀一般發生在定義宏的宏上面。下面是書中的一個例子:定義一個宏的簡稱的的宏。
因為一些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)

參考:
進一步閱讀:

Nested Backquotes considered harmful





來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/133735/viewspace-742882/,如需轉載,請註明出處,否則將追究法律責任。

相關文章