Macros 宏中的clojure引号和波浪线

Macros 宏中的clojure引号和波浪线,macros,clojure,lisp,quoting,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

我是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)
目标是从文件中读取第一个符号,同时避免错误的文本文件名。顺便说一句,这是我的学校作业,所以我不应该用任何其他方法来做,宏必须这样调用,我知道如何使用open
,等等。转义(
~
)只适用于准引号(也称为语法引号)。你需要使用“后引号”(
`
,与大多数美国键盘上的
~
在同一个键上),而不是普通的单引号(
,与
)在同一个键上。这在图形上是一个细微的差别,很容易被忽略

您还可以通过不引用
let
和取消引用
arg1
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))))
`(try
    (let ~arg1 ~arg2)
    (catch Exception ~'e (str "Error: " (.getMessage ~'e))))
(let [e (gensym)]
  `(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__))))
现在,如果我们使用
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 ;; 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))))
`(try
    (let ~arg1 ~arg2)
    (catch Exception ~'e (str "Error: " (.getMessage ~'e))))
(let [e (gensym)]
  `(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__))))
注意使用
~'e
而不仅仅是
e
~
是从准引号中转义出来的,然后我们使用常规引号来引用
e
。这看起来有点奇怪,但很管用

虽然上述解决方案可行,但可以说使用生成的符号比使用
e
更好。这样,如果您更改宏以接受用户为
catch
块提供的代码,则可以确保它仍然有效。在这种特殊情况下,“自动生成”符号非常适合账单。情况如下:

`(try
    (let ~arg1 ~arg2)
    (catch Exception e# (str "Error: " (.getMessage e#))))
基本上,每当Clojure阅读器在准引号形式中遇到带有尾随符号的符号时,它将生成一个新的
gensym
'd符号,并用
gensym
'd符号替换该符号的每次出现(即
e
)。如果我们
macroexpand
this,我们会得到如下结果:

(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__))))
(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))))
如您所见,
e#
的每一次出现都被机器生成的符号所取代。此处
e\uuuuu66\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

最后,虽然auto gen很方便,但它并不总是足够的。主要问题是,由于自动生成符号是在读取时生成的,因此准引号形式的每次评估(即宏的扩展)都将使用相同的自动生成符号。在这种情况下,没关系。但是,在某些情况下,如果使用嵌套宏表单,这可能会导致冲突。在这些情况下,每次展开宏时都需要显式使用
gensym
'd符号。使用这种方法,宏的主体将如下所示:

`(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))))
`(try
    (let ~arg1 ~arg2)
    (catch Exception ~'e (str "Error: " (.getMessage ~'e))))
(let [e (gensym)]
  `(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__))))
此处
e
是宏中的一个局部变量,其值是一个新符号(通过
gensym
)。在准引号中,我们必须转义
e
,以便使用
gensym
'd值

如果我们扩展它,我们将得到如下结果:

(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__))))
(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_u771
被另一个符号(可能是G_u774)替换。相反,自动生成解决方案(
e#
)在每次展开时总是使用相同的符号(至少在我们重新编译宏之前)


希望这能让您更好地理解宏、符号和卫生。如果有什么不清楚的地方,请告诉我。

这里有两个问题:

首先,unsplicing(~和~@)只在语法引号(`)中起作用。通常为宏选择语法引号,因为它也在宏定义位置进行符号命名空间解析。简单引号(')将保持符号完整,因此ns解析将在宏调用站点进行。因为您无法控制宏的调用位置和方式,所以可能会非常混乱

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

(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_u6257_uauto_u_u替换的(避免名称冲突)

您的代码应按以下方式编写:

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

`(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))))
`(try
    (let ~arg1 ~arg2)
    (catch Exception ~'e (str "Error: " (.getMessage ~'e))))
(let [e (gensym)]
  `(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#)))))

您希望宏执行什么操作?更正:
macroexpand
对于调试宏非常有用。例如,
(macroexand'(safe2[s(新文件读取器(新文件“text.txt”)))](.read s))
您可能希望
pprint
结果。您最好