你看這個:
(defmacro once-only ((&rest names) &body body)
(let ((gensyms (loop for n in names collect (gensym))))
`(let (,@(loop for g in gensyms collect `(,g (gensym))))
`(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
,@body)))))
這並不複雜,但它確實有一個嵌套的反引號,和多水平,這是彼此相似,導致易混淆,即使是經驗豐富的Lisp編碼器。
This是宏用來編寫擴展的宏:寫宏的部分宏的宏。
在宏本身中有一個普通的let
,然後一個反向引用的生成的let
它將住在使用once-only
的宏體內。最後,在用戶使用宏的代碼站點中會出現一個雙重反引號let
,它將出現在宏擴展即宏中。
由於once-only
本身就是一個宏,所以它必須是衛生的,因爲它本身就是衛生的;所以它在最外面的let
中爲它自身產生了一堆gensyms。而且,once-only
的目的是爲了簡化另一個衛生宏的編寫。所以它也會爲這個宏產生一個通用。
簡而言之,once-only
需要創建一個宏擴展,它需要一些局部變量,其值爲gensyms。這些局部變量將被用於將gensyms插入到另一個宏擴展中以使其更衛生。而且這些局部變量本身就是衛生的,因爲它們是一個宏觀擴展,所以它們也是神經性的。
如果你正在編寫一個簡單的宏,你必須持有與gensyms局部變量,例如:
;; silly example
(defmacro repeat-times (count-form &body forms)
(let ((counter-sym (gensym)))
`(loop for ,counter-sym below ,count-form do ,@forms)))
在寫宏的過程中,你已經發明瞭一種符號,counter-sym
。該變量在普通視圖中定義。你,人類,選擇它的方式不會與詞彙範圍內的任何事物發生衝突。有問題的詞彙範圍就是你的宏。我們不必擔心counter-sym
意外地捕獲count-form
或forms
中的引用,因爲forms
只是插入到一段代碼中的數據,最終會插入到某個遠程詞法作用域(使用宏的站點)中。我們不必擔心counter-sym
與我們的宏中的另一個變量混淆。例如,我們不能給我們的本地變量名稱count-form
。爲什麼?因爲這個名字是我們的一個函數參數;我們會影響它,造成編程錯誤。
現在,如果您想要一個宏來幫助您編寫該宏,那麼該機器必須與您完成相同的工作。在編寫代碼時,它必須創建一個變量名稱,並且必須注意它的名稱。
但是,與您不同,代碼寫入機器沒有看到周圍的範圍。它不能簡單地查看哪些變量存在,並選擇哪些不會發生衝突。該機器只是一個函數,它需要一些參數(未評估的代碼片段)並生成一段代碼,然後在該機器完成其工作後,將代碼盲目地代入範圍。
因此,機器必須明智地選擇名稱。事實上,爲了完全防彈,它必須是偏執狂,並使用完全獨特的符號:gensyms。
所以繼續這個例子,假設我們有一個機器人爲我們寫這個宏體。這機器人可以是一個宏,repeat-times-writing-robot
:
(defmacro repeat-times (count-form &body forms)
(repeat-times-writing-robot count-form forms)) ;; macro call
什麼可能的機器人宏觀樣子?
(defmacro repeat-times-writing-robot (count-form forms)
(let ((counter-sym-sym (gensym))) ;; robot's gensym
`(let ((,counter-sym-sym (gensym))) ;; the ultimate gensym for the loop
`(loop for ,,counter-sym-sym below ,,count-form do ,@,forms))))
你可以看到這有一定的once-only
的特點:雙嵌套和(gensym)
的兩個層次。如果你能理解這一點,那麼到once-only
的飛躍很小。當然,如果我們只是想讓一個機器人寫出重複次數,我們可以把它作爲一個函數,然後這個函數就不用擔心發明變量了:它不是一個宏,所以它不會「噸需要衛生:
;; i.e. regular code refactoring: a piece of code is moved into a helper function
(defun repeat-times-writing-robot (count-form forms)
(let ((counter-sym (gensym)))
`(loop for ,counter-sym below ,count-form do ,@forms)))
;; ... and then called:
(defmacro repeat-times (count-form &body forms)
(repeat-times-writing-robot count-form forms)) ;; just a function now
但once-only
不能是一個功能,因爲它工作是發明代表了老闆的變量,使用它的宏,函數無法引入變量到它的調用者。
請參閱此處的解釋:https://groups.google.com/forum/?fromgroups#!topic/comp.lang.lisp/F4NVRlOvrX8 – 2012-03-21 17:46:06