轉義(~
)只適用於準-quote(也稱爲語法引用)。您需要使用「back-quote」(`
,在大多數美國鍵盤上的~
上找到相同的密鑰),而不是普通的單引號('
,與"
位於相同的密鑰上)。這是一個微妙的差異,很容易錯過。
您還可以通過不引用let
和未引用arg1
和arg2
來擺脫list
。有了這些變化,我們得到這樣的:
`(try ;; note back-quote, not regular quote.
(let ~arg1 ~arg2) ;; Getting rid of list — not necessary, but most find this more readable
(catch Exception e (str "Error: " (.getMessage e))))
現在,如果我們檢查使用macroexpand
我們的進步:
(macroexand '(safe2 [s (new FileReader (new File "text.txt"))] (.read s)))
我們得到如下:
(try (clojure.core/let [s (new FileReader (new File text.txt))]
(.read s))
(catch java.lang.Exception user/e
(clojure.core/str Error: (.getMessage user/e))))
您可能注意到,在Clojure中,編譯宏時解析了準引號符號。無法解析的符號使用當前名稱空間(在這種情況下爲user
)進行限定。這樣做的基本原理是它可以幫助您編寫「衛生」宏。但是,在這種情況下,我們不想解決e
符號,因爲不能給定局部變量的限定名稱。
我們現在有幾個選項。首先是基本放棄衛生。這適用於這種情況,因爲您不在catch
塊中擴展任何用戶提供的代碼。所以名稱e
可能與用戶變量衝突。該解決方案是這樣的:
`(try
(let ~arg1 ~arg2)
(catch Exception ~'e (str "Error: " (.getMessage ~'e))))
注意使用~'e
,而不是僅僅e
。該~
是爲了擺脫準報價,然後我們使用正規報價引用e
。它看起來有點奇怪,但它有效。
儘管上述解決方案有效,但使用生成的符號代替e
可能更好。這樣,如果您更改宏以接受catch
塊的用戶提供的代碼,則可以確定它仍然可以工作。在這種特殊情況下,「自動生成」符號完美符合法案。這看起來如下:
`(try
(let ~arg1 ~arg2)
(catch Exception e# (str "Error: " (.getMessage e#))))
基本上,只要Clojure的讀者遇到了準報價表內後#
一個符號,它會產生一個新的gensym
「d符號和替換符號的每一次出現(即,e#
)與gensym
'd一個。如果我們macroexpand
這一點,我們會得到這樣的:
(try (clojure.core/let [s (new FileReader (new File text.txt))]
(.read s))
(catch java.lang.Exception e__66__auto__
(clojure.core/str Error: (.getMessage e__66__auto__))))
正如你所看到的,e#
每一次出現用機器生成的符號替換。這裏e__66__auto__
是自動生成的符號。
最後,儘管auto-gen很方便,但並不總是足夠的。主要問題在於,由於自動生成的符號是在讀取時間,所以準引號格式(即宏的擴展)的每個將使用相同的自動生成的符號。在這個特別的情況下,沒關係。但是,如果使用嵌套宏表單,有時會導致衝突。在這些情況下,每次擴展宏時都必須使用明確的gensym
'd符號。通過這種方法,您的宏的身體會是這樣的:
(let [e (gensym)]
`(try
(let ~arg1 ~arg2)
(catch Exception ~e (str "Error: " (.getMessage ~e)))))
這裏e
是在宏的局部變量,它的值是一個新的符號(通過gensym
)。在準引用中,我們必須跳過e
才能使用gensym
的值。如果我們再次擴大這一
(try (clojure.core/let [s (new FileReader (new File text.txt))]
(.read s))
(catch java.lang.Exception G__771
(clojure.core/str Error: (.getMessage G__771))))
,我們會發現G__771
用不同的符號(也許G__774)替換爲:
如果我們擴大這一點,我們會得到什麼樣。相比之下,自動生成的解決方案(e#
)將始終對每個擴展使用相同的符號(至少在我們重新編譯宏之前)。
希望這可以讓你更好地理解宏,符號和衛生。如果有什麼不清楚,請告訴我。
你希望你的宏做什麼? – RedDeckWins 2014-12-19 02:37:58
**更正了:**'macroexpand'對調試宏很有用。例如''(macroexand'(safe2 [s(new FileReader(new File「text.txt」))](.read s)))''你可能想''打印'結果。 – 2014-12-19 04:38:15
你最好把宏命名爲'with-safe'作爲約定;) – myguidingstar 2014-12-19 10:57:55