當在Common Lisp中定義宏的時候,經常會使用到反引號(`)。比方說,我有這麼一個函數git
(defun foobar () (+ 1 1) (+ 2 3) (+ 5 8))
它被調用後會返回最後一個表達式的結果——13。若是我但願在第二個表達式計算後就把結果返回給外部的調用者的話,能夠用return-from
github
(defun foobar () (+ 1 1) (return-from foobar (+ 2 3)) (+ 5 8))
固然了,這屬於沒事找事,由於徹底能夠把最後兩個表達式放到一個prog1
(這也是沒事找事),或者直接點,把最後一個表達式刪掉來作到一樣的效果——但若是是這樣的話這篇東西就寫不下去了,因此我偏要用return-from
。函數
還有一個更加沒事找事的辦法,就是用macrolet
定義一個局部的宏來代替return-from
——我很想把這個新的宏叫作return
,但這樣SBCL會揍我一頓,因此我只好把這個宏叫作bye
(叫作exit
也會被揍).net
(defun foobar () (macrolet ((bye (&optional value) `(return-from foobar ,value))) (+ 1 1) (bye (+ 2 3)) (+ 5 8)))
若是我有另外一個叫作foobaz
的函數code
(defun foobaz () (+ 1 2) (+ 3 4) (+ 5 6))
也想要擁有bye
這種想來就來想走就走的能力的話,能夠依葫蘆畫瓢地包含一個macrolet
blog
(defun foobaz () (macrolet ((bye (&optional value) `(return-from foobaz ,value))) (+ 1 2) (bye (+ 3 4)) (+ 5 6)))
好了,如今我以爲每次都須要在函數體內粘貼一份bye
的實現代碼太麻煩了,想要減小這種重複勞做。因而乎,我打算寫一個宏來幫我複製粘貼代碼。既然要定義宏,那麼首先應當定義這個宏的名字以及用法,姑且是這麼用的吧ip
(with-bye foobar (+ 1 1) (bye (+ 2 3)) (+ 5 8))
with-bye
這個宏須要可以展開成上面的手動編寫的foobar
中的函數體的代碼形式,那麼with-bye
的定義中,就必定會含有macrolet
的代碼,同時也就含有了反引號——好了,如今要來處理嵌套的反引號了。get
這篇文章有個不錯的講解,各位不妨先看看。如今,讓我來機械化地操做一遍,給出with-bye
的定義。首先,要肯定生成的目標代碼中,那一些部分是可變的。對於with-bye
而言,return-from
的第一個參數已經macrolet
的函數體是可變的,那麼不妨把這兩部分先抽象爲參數it
(let ((name 'foobar) (body '((+ 1 1) (bye (+ 2 3)) (+ 5 8)))) `(macrolet ((bye (&optional value) `(return-from ,name ,value))) ,@body))
但這樣是不夠的,由於name
是一個在最外層綁定的,但它被放在了兩層的反引號當中,若是它只有一個前綴的逗號,那麼它就沒法在外層的反引號求值的時候被替換爲目標的FOOBAR
符號。所以,須要在,name
以前再添加一個反引號io
(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
是能用的(笑
可以指導編寫宏的、萬能的、機械化的操做方法,我想應該是不存在的
【閱讀原文】