clojure引号和宏中的波浪号

问题描述 投票:2回答:2

我是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等。

macros clojure lisp quoting
2个回答
8
投票

Escaping(~)仅适用于准引用(也称为语法引用)。你需要使用“反引号”(`,在大多数美国键盘上与~相同的键上找到),而不是正常的单引号(',与"在同一个键上)。这是图形上的细微差别,很容易错过。

你也可以通过不引用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))))

现在,如果我们使用qazxsw poi检查你的进度:

macroexpand

我们得到以下内容:

(macroexpand '(safe2 [s (new FileReader (new File "text.txt"))] (.read s)))

您可能会注意到,在Clojure中,编译宏时会解析准带引号的符号。使用当前命名空间限定无法解析的符号(在本例中为(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)))) )。这样做的理由是它可以帮助您编写“卫生”宏。但是,在这种情况下,我们不想解析user符号,因为局部变量不能被赋予限定名称。

我们现在有几个选择。首先是基本放弃卫生。这适用于这种情况,因为您没有在e块中扩展任何用户提供的代码。因此,catch这个名称与用户变量没有任何关系。此解决方案如下所示:

e

注意使用`(try (let ~arg1 ~arg2) (catch Exception ~'e (str "Error: " (.getMessage ~'e)))) 而不仅仅是~'ee是为了逃避准报价,然后我们使用常规报价来引用~。它看起来有点奇怪,但它确实有效。

虽然上述解决方案有效,但使用生成的符号代替e可能更好。这样,如果您更改宏以接受用户提供的e块代码,您可以确定它仍然有效。在这种特殊情况下,“自动生成”符号完全适合该法案。这看起来如下:

catch

基本上,每当Clojure读者在准报价形式中遇到带有尾随`(try (let ~arg1 ~arg2) (catch Exception e# (str "Error: " (.getMessage e#)))) 的符号时,它将产生一个新的#'d符号,并用gensym的符号替换该符号的每个出现(即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#是自动生成的符号。

最后,虽然auto-gen很方便,但并不总是足够的。主要问题是,由于自动生成符号是在读取时生成的,因此准报价形式的每次评估(即宏的扩展)将使用相同的自动生成符号。在这个特殊情况下,那没关系。但是,在某些情况下,如果使用嵌套的宏窗体,则会导致冲突。在这些情况下,每次扩展宏时都必须使用明确的e__66__auto__'d符号。使用这种方法,宏的主体将如下所示:

gensym

这里(let [e (gensym)] `(try (let ~arg1 ~arg2) (catch Exception ~e (str "Error: " (.getMessage ~e))))) 是宏中的局部变量,其值是一个新的符号(通过e)。在准引用中,我们必须逃避gensym才能使用e'd值。

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

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__774)取代。相比之下,自动生成解决方案(G__771)将始终对每个扩展使用相同的符号(至少在我们重新编译宏之前)。

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


0
投票

这里有两个问题:

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

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

#

注意let如何成为完全限定的clojure.core / let(以后避免ns解析的细微差别),并且x#被x__6257__auto__替换(避免名称冲突)。

你的代码应该写成:

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

(macroexpand-1 '(m)) =>

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

像这样检查:

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

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

(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__))))
© www.soinside.com 2019 - 2024. All rights reserved.