Lisp Quote 和Backquote分析
原文地址 : http://blog.csdn.net/xiaojianpitt/article/details/7747230
對於Lisp初學者來說,最不容易理解的就是Lisp強大的宏。在宏定義中你看到的最多的字元可能是就是引用和反引用。引用和反引用構成了Lisp的基石,所以我先就引用和反引用做一個介紹。宏就留在以後介紹了。
- 引用 (quote)
'(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》所說的:
stackoverflow有一個問答對Lisp的巢狀反引用解釋的很透徹,我們下面來分析一下:
先來說一下巢狀反引用的解析規則:
CLTS2裡面說:
如果反引號是巢狀的話,最內層的反引用形式(這是說的是最內層的逗號,其實是最外層的反引號)應該最先被展開。這意味著如果某個表示式有幾個連續的逗號的話,最左邊的那個逗號屬於最裡面的反引號。
``(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))
- The left backtick got collapsed, so it the (+ 1 2) got escaped by the matching comma (the 2nd comma, according to the HyperSpec).
- On the other hand, the (+ 3 4) didn't have enough commas to get expanded (which is what R5RS mentions).
- 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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Oracle Q-quote delimiter Quote(q) 字串原樣輸出OracleMIT字串
- 【轉】Lisp 已死,Lisp 萬歲!Lisp
- SAP Cloud for Customer裡Sales Order和Sales Quote的建模方式Cloud
- Common lisp HyperSpec lisp 語法幫助Lisp
- 關於lispLisp
- 99-lispLisp
- lisp入門Lisp
- lisp 閉包Lisp
- Lisp 整體思想Lisp
- Lisp 的本質Lisp
- Lisp的誕生Lisp
- Lisp天才神話Lisp
- lisp 習題 occurrencesLisp
- lisp 程式碼示例Lisp
- 【10g SQL新特性】q-quote使用SQL
- keil error:#8:missing closing quote 處理Error
- 99-lisp lisp 的99個問題 P11-20Lisp
- 99-lisp lisp 的99個問題 P1-10Lisp
- lisp 判斷時間Lisp
- Lisp求平方根Lisp
- 向偉大Lisp致敬Lisp
- Common LISP Hints 中文Lisp
- lisp 習題 post+Lisp
- Lisp語言簡介Lisp
- lisp 開發規範。Lisp
- Lisp的永恆之道Lisp
- 用java寫lisp 直譯器 (10 實現物件和類)JavaLisp物件
- MySQL: Couldn't execute 'SET OPTION SQL_QUOTE_SHOW_CREATE=1MySql
- Oracle select 語句字串拼接小例項-quote使用Oracle字串
- Lisp 永遠成不了程式設計主流語言 vs Lisp 神話之路Lisp程式設計
- Lisp經典演算法Lisp演算法
- lisp 變數的作用域Lisp變數
- lisp 中的 【,@】 與 【·】 以及【‘】 以及【 。,】Lisp
- 跨越邊界: Lisp 之美Lisp
- lisp 習題 our-unionLisp
- lisp 支援遠端debugLisp
- windows下搭建lisp環境WindowsLisp
- java如何實現python的urllib.quote(str,safe='/')JavaPython