clojure引号和宏中的波浪号

Nar*_*eva 2 lisp macros clojure quoting

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

这是有效的代码:

(defmacro safe
  [arg1 arg2]
  (list 'let arg1 arg2)
)
Run Code Online (Sandbox Code Playgroud)

这是不起作用的代码

(defmacro safe
    [arg1 arg2]
    '(try
        ~(list 'let arg1 arg2)
        (catch Exception e (str "Error: " (.getMessage e)))
    )
)
Run Code Online (Sandbox Code Playgroud)

~符号之后,它应该逃脱引号,但由于某种原因,它似乎没有.错误是:"无法解析符号:此上下文中的arg1 ......".

谢谢你的帮助!


编辑:

我用以下代码调用宏的代码:

(println (safe [s (new FileReader (new File "text.txt"))] (.read s)))
Run Code Online (Sandbox Code Playgroud)

另外,我导入这个:

(import java.io.FileReader java.io.File)
Run Code Online (Sandbox Code Playgroud)

目标是从文件中读取第一个符号,同时避免不正确的文本文件名.顺便说一句,这是我的学校作业,所以我不应该用任何其他方式来做这个,并且必须像这样调用宏,我知道with-open等等.

Nat*_*vis 8

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

您也可以摆脱的list由不引用的let和未引用arg1arg2.通过这些更改,我们得到以下内容:

`(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))))
Run Code Online (Sandbox Code Playgroud)

现在,如果我们使用macroexpand以下方法检查进度:

(macroexpand '(safe2 [s (new FileReader (new File "text.txt"))] (.read s)))
Run Code Online (Sandbox Code Playgroud)

我们得到以下内容:

(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))))
Run Code Online (Sandbox Code Playgroud)

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

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

`(try
    (let ~arg1 ~arg2)
    (catch Exception ~'e (str "Error: " (.getMessage ~'e))))
Run Code Online (Sandbox Code Playgroud)

注意使用~'e而不是仅仅e.这~是为了逃避准报价,然后我们使用常规报价来引用e.它看起来有点奇怪,但它确实有效.

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

`(try
    (let ~arg1 ~arg2)
    (catch Exception e# (str "Error: " (.getMessage e#))))
Run Code Online (Sandbox Code Playgroud)

基本上,每当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__))))
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,每次出现都会e#被机器生成的符号所取代.这e__66__auto__是自动生成的符号.

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

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

e是宏中的局部变量,其值为新符号(via 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))))
Run Code Online (Sandbox Code Playgroud)

如果我们再次扩展它,我们会发现G__771用不同的符号(可能是G__774)代替.相反,自动生成的解决方案(e#)将始终对每个扩展使用相同的符号(至少在我们重新编译宏之前).

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