转义(~
)只适用于准-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