宏展开形式求值,但非展开形式抛出异常

Macroexpanded form evaluates, but non-expanded form throws exception

如果这很重要,我正在使用 CIDER。

(defmacro trace [prompt x]
  (let [p (subs prompt 4)
        expanded (macroexpand x)]
    (cond (seq? expanded) `(do (println ~p '~x "...")
                               (let [result ~(map #(if (or (not (symbol? %)) (function? %))
                                                     (list 'trace (join [prompt prompt]) %)
                                                     %) expanded)] 
                               (println ~p result "->" ~expanded))
                               ~expanded)
          :else expanded)))

那是我正在处理的宏,但它应该无关紧要(尽管它可能确实如此)。

这是导致问题的特定代码

(trace "    " (if true 6 4))

直接求值抛出异常:

Can't let qualified name: clj-match.trace/result

我宏扩展了表单来调试它,我得到了这个:

(do
  (println "" '(if true 6 4) "...")
  (let* [result (if true 6 4)] (println "" result "->" (if true 6 4)))
  (if true 6 4))

这看起来一点也不差,所以我尝试评估扩展形式。令人惊讶的是它起作用了,评估为 6.

为什么会这样?

更重要的是,我做错了什么以获得异常?

首先是第二个问题:您遇到了一个异常,因为您确实在尝试 let 一个合格的名称,特别是 clj-match.trace/result.

发生这种情况是因为宏在 seq? 情况下扩展成的语法引用 (do …) 形式使用 result 作为 [=10= 中本地绑定的名称] 它产生的形式。文字 result 符号将由 reader 进行命名空间限定,因为它出现在语法引用形式内,因此最终结果将是 (let [clj-match.trace/result …] …),这是不正确的(名称let 绑定不能是命名空间限定的)。您可以使用 result# 来避免这种情况,或者明确地 gensym 语法引用形式之外的符号并取消引用以使用它。

(顺便说一句,您可能希望在扩展中语法引用您的 trace 符号,而不仅仅是引用,以确保它实际上是指您的宏,而不管扩展所在的上下文发生。)

至于您扩展宏的实验——以您的表单作为参数的常规 macroexpand-1 调用将显示上述内容,因此大概您使用了 CIDER 或其他 Emacs 包提供的一些工具来内联扩展它?可能那个设施有点问题。

`reader 宏将其主体内的所有符号扩展为命名空间限定。

user=> (macroexpand `(let [a 0] a))
(let* [user/a 0] user/a)

user/a 不是有效的本地绑定。

解决方案是使用 `.

的 gensym shorthand 特性
user=> (macroexpand `(let [a# 0] a#))
(let* [a__3__auto__ 0] a__3__auto__)

这对人类来说更难阅读,但实际上生成了有效代码。