當在common lisp中定義巨集的時候,常常會使用到反引號(`)。比方說,我有這麼乙個函式
(defun foobar ()
(+ 1 1)
(+ 2 3)
(+ 5 8))
它被呼叫後會返回最後乙個表示式的結果——13。如果我希望在第二個表示式計算後就把結果返回給外部的呼叫者的話,可以用return-from
(defun foobar ()
(+ 1 1)
(return-from foobar (+ 2 3))
(+ 5 8))
當然了,這屬於沒事找事,因為完全可以把最後兩個表示式放到乙個prog1
(這也是沒事找事),或者直接點,把最後乙個表示式刪掉來做到同樣的效果——但如果是這樣的話這篇東西就寫不下去了,所以我偏要用return-from
。
還有乙個更加沒事找事的辦法,就是用macrolet
定義乙個區域性的巨集來代替return-from
——我很想把這個新的巨集叫做return
,但這樣sbcl會揍我一頓,所以我只好把這個巨集叫做bye
(叫做exit
也會被揍)
(defun foobar ()
(macrolet ((bye (&optional value)
`(return-from foobar ,value)))
(+ 1 1)
(bye (+ 2 3))
(+ 5 8)))
如果我有另乙個叫做foobaz
的函式
(defun foobaz ()
(+ 1 2)
(+ 3 4)
(+ 5 6))
也想要擁有bye
這種想來就來想走就走的能力的話,可以依葫蘆畫瓢地包含乙個macrolet
(defun foobaz ()
(macrolet ((bye (&optional value)
`(return-from foobaz ,value)))
(+ 1 2)
(bye (+ 3 4))
(+ 5 6)))
好了,現在我覺得每次都需要在函式體內貼上乙份bye
的實現**太麻煩了,想要減少這種重複勞作。於是乎,我打算寫乙個巨集來幫我複製貼上**。既然要定義巨集,那麼首先應當定義這個巨集的名字以及用法,姑且是這麼用的吧
(with-bye foobar
(+ 1 1)
(bye (+ 2 3))
(+ 5 8))
with-bye
這個巨集需要能夠展開成上面的手動編寫的foobar
中的函式體的**形式,那麼with-bye
的定義中,就一定會含有macrolet
的**,同時也就含有了反引號——好了,現在要來處理巢狀的反引號了。
這篇文章有個不錯的講解,各位不妨先看看。現在,讓我來機械化地操作一遍,給出with-bye
的定義。首先,要確定生成的目標**中,那一些部分是可變的。對於with-bye
而言,return-from
的第乙個引數已經macrolet
的函式體是可變的,那麼不妨把這兩部分先抽象為引數
(let ((name 'foobar)
(body '((+ 1 1) (bye (+ 2 3)) (+ 5 8))))
`(macrolet ((bye (&optional value)
`(return-from ,name ,value)))
,@body))
但這樣是不夠的,因為name
是乙個在最外層繫結的,但它被放在了兩層的反引號當中,如果它只有乙個字首的逗號,那麼它就無法在外層的反引號求值的時候被替換為目標的foobar
符號。因此,需要在,name
之前再新增乙個反引號
(let ((name 'foobar)
(body '((+ 1 1) (bye (+ 2 3)) (+ 5 8))))
`(macrolet ((bye (&optional value)
`(return-from ,,name ,value)))
,@body))
如果你在emacs中對上述的表示式進行求值,那麼它吐出來的結果實際上是
(macrolet ((bye (&optional value)
`(return-from ,foobar ,value)))
(+ 1 1)
(bye (+ 2 3))
(+ 5 8))
顯然,這還是不對。如果生成了上面這樣的**,那麼對於bye
而言foobar
就是乙個未繫結的符號了。之所以會這樣,是因為
name
在繫結的時候輸入的是乙個符號,並且
name
被用在了巢狀的反引號內,它會被求值兩次——第一次求值得到符號foobar
,第二次則是foobar
會被求值
因此,為了對抗第二次的求值,需要給,name
加上乙個字首的引號(『),最終效果如下
(let ((name 'foobar)
(body '((+ 1 1) (bye (+ 2 3)) (+ 5 8))))
`(macrolet ((bye (&optional value)
`(return-from ,',name ,value)))
,@body))
所以with-bye
的定義是這樣的
(defmacro with-bye (name &body body)
`(macrolet ((bye (&optional value)
`(return-from ,',name ,value)))
,@body))
我大言不慚地總結一下,剛才的操作步驟是這樣的。首先,找出一段有規律的、需要被用巨集來實現的目標**;然後,識別其中的可變的**,給這些可變的**的位置起乙個名字(例如上文中的name
和body
),將它們作為let
表示式的繫結,把目標**裝進同乙個let
表示式中。此時,目標**被加上了一層反引號,而根據每個名字出現的位置的不同,為它們適當地補充乙個字首的逗號;最後,如果在巢狀的反引號**現的名字無法被求值多次——比如符號或者列表,那麼還需要給它們在第乙個逗號後面插入乙個引號,避免被求值兩次招致未繫結的錯誤。
就用上面所引用的文章裡的例子好了。有一天我覺得common lisp中一些常用的巨集的名字實在是太長了想要精簡一下——畢竟敲鍵盤也是會累的——假裝沒有自動補全的功能。我可能會定義下面這兩個巨集
(defmacro d-bind (&body body)
`(destructuring-bind ,@body))
(defmacro mv-bind (&body body)
`(multiple-value-bind ,@body))
顯然,這裡的**的寫法出現了重複模式,不妨試用按照機械化的操作手法來提煉出乙個巨集。第一步,先識別出其中可變的內容。對於上面這個例子而言,變化的地方其實只有兩個名字——新巨集的名字(d-bind
和mv-bind
),以及舊巨集的名字(destructuring-bind
和multiple-value-bind
)。第二步,給它們命名並剝離成let
表示式的繫結,得到如下的**
(let ((new-name 'd-bind)
(old-name 'destructuring-bind))
`(defmacro ,new-name (&body body)
`(,old-name ,@body)))
因為old-name
處於巢狀的反引號中,但是它是由最外層的let
定義的,所以應當添上乙個字首的逗號,得到
(let ((new-name 'd-bind)
(old-name 'destructuring-bind))
`(defmacro ,new-name (&body body)
`(,,old-name ,@body)))
最後,因為old-name
繫結的是乙個符號,不能被兩次求值(第二次是在defmacro
定義的新巨集中展開,此時old-name
已經被替換為了destructuring-bind
,而它對於新巨集而言是乙個自由變數,並沒有被繫結),所以需要有乙個單引號來阻止第二次的求值——因為需要的就是符號destructuring-bind
本身。所以,最終的**為
(defmacro define-abbreviation (new-name old-name)
`(defmacro ,new-name (&body body)
`(,',old-name ,@body)))
試一下就可以確認這個define-abbreviation
是能用的(笑
能夠指導編寫巨集的、萬能的、機械化的操作方法,我想應該是不存在的
Shell中的引號,反引號,雙引號,反斜槓
轉貼自 http hi.baidu.com kfeng21 blog item 3b3fcc3fc1bc85f2838b131e.html shell可以識別4種不同型別的引字符號 單引號字元 雙引號字元 反斜槓字元 反引號字元 1.單引號 howard 0 script grep susan ph...
shell中單引號 雙引號 反引號 反斜槓的區別
shell 可以識別4種不同型別的引字符號 單引號字元 雙引號字元 反斜槓字元 反引號字元 1.單引號 grep susan phonebook susan goldberg 403 212 4921 susan topple 212 234 2343 如果我們想查詢的是susan goldberg...
shell中單引號 雙引號 反引號 反斜槓的區別
url shell 可以識別4種不同型別的引字符號 單引號 字元 雙引號字元 反斜槓字元 反引號字元 color red b 1.單引號 b color grep susan phonebook susan goldberg 403 212 4921 susan topple 212 234 234...