clojure引号和宏中的波浪号

时间:2014-12-19 02:21:08

标签: macros clojure lisp quoting

我是Clojure的新手,我无法理解其报价系统。我正在写一个宏,我做了两个相似的案例 - 一个是有效的,另一个则没有。从某种意义上说,我只是试图用try / catch条件包围我的语句。

以下是有效的代码:

(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等。

2 个答案:

答案 0 :(得分:8)

转义(~)仅适用于准引用(也称为语法引用)。您需要使用“返回引用”(`,在与大多数美国键盘上的~相同的键上找到),而不是正常的单引号(',它是打开的与"相同的密钥。这是图形上的细微差别,很容易错过。

您也可以通过不引用list和不引用letarg1来摆脱arg2。通过这些更改,我们得到类似的结果:

`(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检查我们的进度:

(macroexpand '(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}。如果我们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很方便,但并不总是足够。主要问题是,因为自动生成的符号是在时生成的,所以准报价形式的每个评估(即扩展 em> of macro)将使用相同的自动生成符号。在这个特殊情况下,那没关系。但是,在某些情况下,如果使用嵌套的宏窗体,则会导致冲突。在这些情况下,每次扩展宏时都必须使用明确的gensym'd符号。使用这种方法,宏的主体将如下所示:

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

此处e是宏中的局部变量,其值为新鲜符号(通过gensym)。在准报价中,我们必须转义e才能使用gensym'd值。

如果我们扩展这个,我们会得到类似的东西:

(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#)将始终对每个扩展使用相同的符号(至少在我们重新编译宏之前)。

希望这可以让您更好地了解宏,符号和卫生。如果有什么不清楚,请告诉我。

答案 1 :(得分:0)

这里有两个问题:

首先,unsplicing(〜和〜@)仅在syntax-quote(`)中起作用。通常为宏选择语法引用,因为它还在宏定义位置执行符号命名空间解析。简单引号(')将保持符号不变,因此ns解析将在宏调用站点发生。因为您无法控制调用宏的位置和方式,所以可能会非常混乱。

其次,您不能仅在引用代码中声明新符号,这可能会导致名称与宏周围的代码冲突。宏引入的每个新符号都应使用后缀#,因此Clojure宏扩展将使用新的自动生成的名称替换它,这不会导致任何名称与用户代码冲突。

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

(macroexpand-1 '(m)) =>

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

注意let如何成为完全限定的clojure.core / let(以后避免ns解析的细微差别),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__))))

我还建议使用宏args的惯用名并制作任意长度的第二个参数:

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