2013-12-24 33 views
3

Clojure的宏是一個困難點對我來說, 這裏是一個宏觀例如,從「之實踐的Clojure」了:混淆關於Clojure的宏定義

(defmacro triple-do [form] 
    (list 'do form form form)) 

user=> (triple-do (println "test")) 

test 

test 

test 

nil 

這三做效果很好 我認爲以下版本應該可以工作但不是

(defmacro triple-do [form] 
    (do form form form)) 

user=> (triple-do (println "test")) 

test 

nil 

爲什麼它只是打印一次?

和下面讓我困惑極

(defmacro test-macro [form] (do form (println "hard code test"))) 


user=> (test-macro (println "hello")) 

hard code test 

nil 

爲什麼「你好」不是在控制檯顯示?

回答

2

這將有助於通過macroexpand的鏡頭來查看您的示例,它是調試宏的基本工具。

例1:

(defmacro triple-do [form] 
    (list 'do form form form)) 

user=> (triple-do (println "test")) 

記住宏應該返回,將在運行時執行的列表。讓我們來看看這個宏的回報:

(macroexpand '(triple-do (println "test"))) 
;; (do (println "test") (println "test") (println "test")) 

所以也沒有執行代碼,而是返回表示一旦展開宏將要執行的代碼清單。這類似於在REPL嘗試下面的代碼片段:記住

(+ 1 2 3) 
;; 6 

(list '+ 1 2 3) 
;; (+ 1 2 3) 

有了這個,讓我們來看例2:

(defmacro triple-do [form] 
    (do form form form)) 

user=> (triple-do (println "test")) 

注意如何宏現在不返回一個列表。它只是執行do形式返回的最後一條語句是傳遞的形式,這可以通過擴展宏很容易地看到:

(macroexpand '(triple-do (println "test"))) 
;; (println "test") 

這就是爲什麼你最終有一個單一的打印語句。

這應該給你一個關於例3的線索:

(defmacro test-macro [form] (do form (println "hard code test"))) 


user=> (test-macro (println "hello")) 

這是一個有點棘手,但我們仍然展開:

(macroexpand '(test-macro (println "hello"))) 
;; hard code test <= this gets print before the macro fully expands 
;; nil <= the expansion yields nil 

再次,因爲你沒有返回一個列表,而是隻需執行一個do表單,它只是在宏內運行println調用,並且由於println返回nil,這就是擴展的結果。

爲了說明我的觀點,這是你必須如何修改您的宏,以達到所需的行爲:

(defmacro test-macro [form] (list 'do form (println "hard code test"))) 
(test-macro (println "hello")) 
;; hard code test 
;; hello 

我希望這會清除你的東西。

只要記住這個:宏應該返回表示您希望在運行時執行的代碼的列表

+0

謝謝leonardoborges。我很清楚 – user3131318

+0

我很高興它有幫助。請考慮接受這個答案,如果它清除了足夠的東西給你。謝謝。 – leonardoborges

2

宏必須在編譯時返回將在代碼中佔據其位置的列表。

因爲do會採用任意數量的參數並返回最後一個,在您的每種情況下,宏都會擴展爲do塊中的最後一個格式。

原始返回一個do作爲第一個元素的列表,所以不是返回塊中的最後一個元素,而是擴展到整個塊。

4

宏是函數,它返回一個Clojure s表達式,然後編譯它,或者如果宏返回一個宏,則再次展開它。這個替換過程重複遞歸直到沒有宏保留,然後評估最終代碼。與最終生成的代碼運行相比,它有助於仔細考慮宏在擴展時運行的是什麼。

macroexpand-1功能可以爲您展示一個宏展開真正幫助:

user> (macroexpand-1 '(test-macro (println "hello"))) 
hard code test 
nil 

從這個可以看到,雖然宏擴展打印語句正在發生的事情。如果你在做之前添加語法引用,宏可能更有意義。

user> (defmacro test-macro [form] `(do ~form (println "hard code test"))) 
#'user/test-macro 
user> (macroexpand-1 '(test-macro (println "hello"))) 
(do (println "hello") (clojure.core/println "hard code test")) 

在這種情況下,打印在宏完成展開後運行。

+0

感謝亞瑟,這很有幫助 – user3131318