2014-10-17 83 views
2

這裏是一個簡短的片段演示了這個問題:使用宏生成方法時出現奇怪的錯誤?

(defmulti test-dummy type) 

(defmacro silly [t] 
    `(defmethod test-dummy ~(resolve t) [some-arg] "FOO!")) 

(silly String) 

評估結果爲「不能使用合格的名稱作爲參數:用戶/一些-ARG」,但運行macroexpand給出了一個完美的結果:

(defmethod test-dummy java.lang.String [some-arg] "FOO!") 

在參數名稱之前鍵入〜'以使其評估爲符號作品,但發生了什麼?

+1

也許你可以使用autogensym來避免這種情況。在宏定義中將'some-arg'改爲'some-arg#'。 – coredump 2014-10-17 10:16:53

+0

好吧,但爲什麼會發生?爲什麼我從macroexpand獲得正確的表達式? – user3026691 2014-10-17 12:31:39

+1

不幸的是,'macroexpand'函數與編譯器不是100%兼容的。也就是說,我沒有看到相同的宏擴展:'(.user/test-dummy clojure.core/addMethod java.lang.String(clojure.core/fn [user/some-arg]「FOO!」)) ' - 這個命名空間符合'some-arg',這是' – noisesmith 2014-10-17 14:08:14

回答

4

好的。所以這裏的問題是Clojure試圖通過確保宏的擴展中沒有符號可以從宏的擴展環境中捕獲的不合格的本地人來制定macro hygiene

傳統上,Lisp方言允許宏擴展包含任意符號。這會產生以下問題,其中包含要展開的宏的表達式定義了符號some-arg,其在宏的展開結果中沒有定義地使用。這意味着這個宏正在從它的擴展環境中「捕獲」一個符號/值,這是很少需要的行爲。這正是Clojure編譯器認爲在這裏與你的符號some-arg。 Clojure編譯器嘗試將some-arg解析爲名稱空間級別符號(以前的定義或需要爲符號some-var創建別名),並且無法這樣做,從而生成user/some-arg未定義的警告。

這個問題有兩種解決方案。首先是使用宏擴展系統知道的some-arg表示本地並且不會嘗試解析。

(defmacro silly [t] 
    `(defmethod test-dummy ~(resolve t) [some-arg#] "FOO!")) 

另一種方法是,你可以使用宏接頭操作~插入引用符號的值。

(defmacro silly [t] 
    `(defmethod test-dummy ~(resolve t) [~'some-arg] "FOO!")) 

在這兩種情況下,您必須在符號的所有用途中使用相同的表達式(gensym或splice)。顧名思義就是顧名思義會產生一個符號來使用,因此不會產生可重複的命名。這是用於轉義符號衝突的功能。然而,拼接可以讓你始終生成一個指定的符號,以防你需要一個真正的人類可用的名字(比如def),或者你確實想明確地從環境中關閉某些東西。

+0

啊!這就說得通了。謝謝。我錯誤地認爲backtick的運行方式與Common Lisp相同。 – user3026691 2014-10-17 14:24:55

相關問題