2014-12-19 41 views
2

我是Clojure的新手,我無法理解其引用系統。我寫了一個宏,我做了兩個類似的例子 - 一個正常,另一個不正常。從某種意義上說,我只是試圖圍繞我的聲明與try/catch條件。clojure引號和宏代碼

下面是工作代碼:

(defmacro safe 
    [arg1 arg2] 
    (list 'let arg1 arg2) 
) 

這裏有沒有在~符號後,工作

(defmacro safe 
    [arg1 arg2] 
    '(try 
     ~(list 'let arg1 arg2) 
     (catch Exception e (str "Error: " (.getMessage e))) 
    ) 
) 

的代碼,它應該逃脫報價,但由於某些原因,它似乎就像它沒有。錯誤是:「在這種情況下無法解析符號:arg1 ...」。

感謝您的幫助!


編輯:我打電話與宏

代碼:

(println (safe [s (new FileReader (new File "text.txt"))] (.read s))) 

另外,我導入此:

(import java.io.FileReader java.io.File) 

目標是從文件中讀取第一個符號,同時避免不正確的文本文件名稱。這是我的學校作業順便說一下,所以我不應該用任何其他方式來做到這一點,宏必須被稱爲那樣,我知道關於with-open

+0

你希望你的宏做什麼? – RedDeckWins 2014-12-19 02:37:58

+0

**更正了:**'macroexpand'對調試宏很有用。例如''(macroexand'(safe2 [s(new FileReader(new File「text.txt」))](.read s)))''你可能想''打印'結果。 – 2014-12-19 04:38:15

+0

你最好把宏命名爲'with-safe'作爲約定;) – myguidingstar 2014-12-19 10:57:55

回答

6

轉義(~)只適用於準-quote(也稱爲語法引用)。您需要使用「back-quote」(`,在大多數美國鍵盤上的~上找到相同的密鑰),而不是普通的單引號(',與"位於相同的密鑰上)。這是一個微妙的差異,很容易錯過。

您還可以通過不引用let和未引用arg1arg2來擺脫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#)將始終對每個擴展使用相同的符號(至少在我們重新編譯宏之前)。

希望這可以讓你更好地理解宏,符號和衛生。如果有什麼不清楚,請告訴我。

+0

謝謝你的迴應!但是,它會產生新的錯誤(「無法綁定限定名稱:用戶/ e,編譯..」)。我用我調用宏的代碼更新了我的問題,也許測試會更容易,再次感謝! – 2014-12-19 02:56:41

+0

請參閱我在有關'macroexpand'的問題上留下的評論。 – 2014-12-19 03:03:19

+0

我做了macroexpand,但我仍然有同樣的錯誤 – 2014-12-19 03:16:28

0

有兩個問題在這裏:

首先,unsplicing(〜和〜@)只有語法引號(')內工作。語法quote通常是爲宏選擇的,因爲它也在宏定義位置進行符號名稱空間解析。簡單的引號(')會使符號保持不變,所以ns的解析會在宏調用站點發生。由於您無法控制您的宏將被調用的位置和方式,因此可能會非常混亂。其次,你不能在引用的代碼中聲明新的符號,它可能會導致名稱與宏周圍的代碼發生衝突。宏引入的每個新符號都應使用後綴#,以便Clojure宏展開將用新的自動生成的名稱替換它,該名稱不會導致與用戶代碼發生任何名稱衝突。

(defmacro m [] 
`(let [x# 1] 
    x#)) 

(macroexpand-1 '(m)) => 

=> (clojure.core/let [x__6257__auto__ 1] 
    x__6257__auto__) 

注讓利如何(以後避免納秒分辨率的細微差別)成爲完全合格clojure.core /讓,而x#得到替換x__6257__auto__(避免名稱衝突)。

你的代碼應該寫成這樣:

(defmacro safe [arg1 arg2] 
`(try 
    (let ~arg1 ~arg2) 
     (catch Exception e# 
     (str "Error: " (.getMessage e#))))) 

檢查這樣的:

(macroexpand-1 '(safe [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 e__6283__auto__ 
    (clojure.core/str "Error: " (.getMessage e__6283__auto__)))) 

我也將電子書籍使用慣用的名稱爲宏指定參數和製造任意lenght的第二個參數:

(defmacro safe-let [bindings & body] 
`(try 
    (let ~bindings 
     [email protected]) 
     (catch Exception e# 
     (str "Error: " (.getMessage e#)))))