clojure引号和宏代码中的代字符

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


转义( ~ )仅适用于准引号(也称为语法引用)。 您需要使用“back-quote”( ` ,在与大多数美国键盘相同的键上找到~ ),而不是普通的单引号( ' ,与'相同的键" )。 ,这可能很容易错过。

您也可以通过不引用let和un-quoting 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倒是一个。 如果我们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__是自动生成的符号。

最后,虽然自动发电很方便,但并不总是足够的。 主要问题是,由于自动生成的符号是在读取时生成的,因此对每个准引号形式的评估(即宏的扩展)将使用相同的自动生成的符号。 在这个特别的情况下,没关系。 但是,如果使用嵌套宏表单,有时会导致冲突。 在这些情况下,每次扩展宏时都必须使用明确的gensym符号。 用这种方法,你的宏的主体看起来像这样:

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

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


这里有两个问题:

首先,unsplicing(〜和〜@)只能在syntax-quote(`)内部工作。 语法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__))))

我还会推荐使用宏指令的惯用名称,并提供任意长度的第二个参数:

(defmacro safe-let [bindings & body]
 `(try
    (let ~bindings
      ~@body)
      (catch Exception e#
        (str "Error: " (.getMessage e#)))))
链接地址: http://www.djcxy.com/p/65761.html

上一篇: clojure quotes and tilde in macros

下一篇: Having trouble with clojure macro