【问题标题】:Can try and catch be in different (but nested) macros?try and catch 可以在不同的(但嵌套的)宏中吗?
【发布时间】:2012-10-29 21:07:23
【问题描述】:

try 在一个宏中,catch 在第二个宏中,由第一个宏调用。如何让以下工作?

(defmacro catch-me []
  `(catch ~'Exception ~'ex
     true))

(defmacro try-me []
  `(try (+ 4 3)
        (catch-me)))

扩展 try-me 看起来不错:

(clojure.walk/macroexpand-all '(try-me))

产量

(try (clojure.core/+ 4 3) (catch Exception ex true))

但调用 (try-me) 会产生:

"Unable to resolve symbol: catch in this context",

顺便说一句,这也是您在未尝试使用 catch 时会在 REPL 中收到的消息。

更新:

这就是我如何让它工作(感谢@Barmar),在这里你可以看到我的代码的实际上下文:

(defmacro try-me [& body]
  `(try
     ~@body
     ~@(for [[e msg] [[com.mongodb.MongoException$Network "Database unreachable."]
                      [com.mongodb.MongoException "Database problem."]
                      [Exception "Unknown error."]]]
         `(catch ~e ~'ex
            (common/site-layout
             [:div {:id "errormessage"}
              [:p ~msg]
              [:p "Error is: " ~e]
              [:p "Message is " ~'ex]])))))

但这正是我所希望的(使用单独的宏 catch-me):

(defmacro try-me [& body]
  `(try
     ~@body
     (catch-me com.mongodb.MongoException$Network "Database unreachable.")
     (catch-me com.mongodb.MongoException "Database problem.")
     (catch-me Exception "Unknown error.")))

我认为这会更容易编写/维护。

有什么想法吗?我需要语法引用,因为我正在传递参数,这就是为什么不幸的是 Arthur 的答案无法应用(或者可以以某种方式应用?),但我直到现在才发布我的实际上下文。

【问题讨论】:

  • 我目前的猜测是 (catch-me) 首先展开,然后才明确它在 try 中。是这样吗?如何对抗?
  • 宏只在可以正常计算表达式的地方展开。 catch 子表单不是被评估的表达式,它们是 try 语法的一部分。
  • 我目前仍然认为这个问题没有答案。令人费解的是,为什么 Arthur 的示例有效,但使用语法引用的示例无效,而且,macroexpand-all 显示了一个有效的宏扩展,但直接调用它失败并出现错误。感谢 Barmar 的解决方法。还有其他想法吗?

标签: clojure macros try-catch


【解决方案1】:

您收到该错误的原因是try 的语法是:

(try expr* catch-clause* finally-clause?)

这意味着在 catch 和 finally 子句之前可以有任意数量的 expr 形式。 try 扫描exprs,直到找到以catch 或finally 开头的一个。它在扩展任何宏之前执行此操作,因为它只是试图找出 expr 和 catch/finally 子句从哪里开始。它收集所有catch 和finally 子句,并为它们建立适当的错误处理环境。

一旦这样做,它就会正常执行所有expr 表单。所以它扩展了它们的宏,然后执行它们。但是catch 不是函数或特殊形式,它只是try 在前面的步骤中寻找的东西。所以当它正常执行时,你会得到与在 REPL 中键入时相同的错误。

您可能应该做的是编写一个宏,将您的整个代码包装起来,扩展成您想要的try/catch 表达式。如果没有您要完成的工作的示例,很难给出具体的答案。

【讨论】:

  • 我明白你在说什么,但感觉问题出在catch 被评估得太早而不是太晚,这就是我阅读你的答案的方式,你不同意吗?这也可以解释为什么@arthur 的答案(我同意他的观点,这不是最漂亮的解决方案)有效:catch 已拼接但尚未评估。我正在努力实现这一点,并且仍然使用语法引用:-) 我确实跟进了你的建议,并将我的所有代码都包装在一个宏中,要么发布它,要么,如果我找到它,一个更漂亮的解决方案 :-)
  • 问题是 catch 根本不应该被评估,没有“太快”。 catch 和 finally 不是真正的运算符,即使它们的编写方式与它们一样。编译器在扫描try 的正文时逐字查找它们。
  • 对,但我认为在宏处理期间,尝试最初只是“粘贴”(引用),这可以解释为什么@Arthur 的解决方案有效(即使不适合我)?无论如何,我发现 macroexpand-all 显示了一个有效的扩展但它仍然不起作用,这让我感到困惑。
  • 我想说这是macroexpand-all 中的一个错误,但它似乎无法修复。 try 正文中不以 catch 或 finally 开头的任何内容都必须进行宏扩展。因此,当它完成时,无法判断这些catch 子表达式不是原始形式,这是它们需要被识别的地方。很遗憾try 的语法是这样的模棱两可。
  • 我明白你在说什么,这对我来说似乎不对,这也与@Arthur 的示例有效的事实相矛盾,不是吗?
【解决方案2】:

简短的回答是肯定的,尽管嵌套具有特殊形式的宏可能会导致一些像这样的双引号问题。必须防止符号在两个扩展级别上被评估:

user> (defmacro catch-me []                                   
          '(list 'catch 'Exception 'ex  
                       'true))

user> (defmacro try-me []
    `(try (+ 4 3)              
                  ~(catch-me)))
#'user/try-me

user> (try-me)
7

并查看它是否也捕获了异常:

user> (defmacro try-me []
    `(try (/ 4 0)
              ~(catch-me)))
#'user/try-me
user> (try-me)
true

【讨论】:

  • 由于您的建议和(clojure.walk/macroexpand-all '(try-me)) 都有效,我仍在尝试找到一个使用语法引用的方法。如果我找到它或只是跟进@Barmar 的建议,我会发布。
猜你喜欢
  • 1970-01-01
  • 2016-12-09
  • 1970-01-01
  • 1970-01-01
  • 2020-01-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-12
相关资源
最近更新 更多