【问题标题】:clojure quotes and tilde in macros宏中的 clojure 引号和波浪号
【发布时间】:2014-12-19 02:21:07
【问题描述】:

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

【问题讨论】:

  • 你希望你的宏做什么?
  • 已更正: macroexpand 对于调试宏非常有用。例如,(macroexand '(safe2 [s (new FileReader (new File "text.txt"))] (.read s))) 你可能想要pprint 结果。
  • 您最好将宏命名为with-safe ;)

标签: macros clojure lisp quoting


【解决方案1】:

转义 (~) 仅适用于准引号(也称为语法引号)。您需要使用“反引号”(`,与大多数美国键盘上的 ~ 在同一个键上),而不是普通的单引号(',与 @987654325 在同一个键上@)。这是图形上的细微差别,很容易被忽略。

您也可以通过不引用 let 和取消引用 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 检查我们的进度:

(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'd 之一。如果我们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__ 是自动生成的符号。

最后,虽然自动生成很方便,但它并不总是足够的。主要问题是,由于自动生成的符号是在 read 时生成的,因此准引号形式的每次 求值(即,展开 em> 宏)将使用相同的自动生成符号。在这种特殊情况下,这很好。但是,在某些情况下,如果使用嵌套宏形式,这可能会导致冲突。在这些情况下,每次扩展宏时都需要显式使用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#) 将始终对每次扩展使用相同的符号(至少在我们重新编译宏之前)。

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

【讨论】:

  • 感谢您的回复!但是,它会产生一个新错误(“无法绑定限定名:用户/e,正在编译..”)。我用调用宏的代码更新了我的问题,也许它会更容易测试,再次感谢!
  • 查看我对macroexpand问题留下的评论。
  • 抱歉,我忘记在我的macroexpand 示例中引用println 表格。在这种情况下,您需要将输入引用到macroexpand,因为它是一个常规函数,以防止它首先被评估。
  • 另外,您希望宏展开 safe 表单,而不是 println 表单。我发布了一条带有更正 macroexpand 调用的新评论。
  • 是否使用像 e# 这样的 gensym 名称而不是 e 工作?
【解决方案2】:

这里有两个问题:

首先,解拼接(~ 和 ~@)仅在语法引号 (`) 内有效。通常为宏选择语法引用,因为它还在宏定义位置进行符号命名空间解析。简单的引号 (') 将保持符号完整,因此 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#)))))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-29
    • 2011-09-22
    • 2019-07-07
    • 2019-04-09
    • 1970-01-01
    • 2012-12-04
    • 2021-09-15
    相关资源
    最近更新 更多