2014-12-19 93 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#)))))