2024 Clojure 状态调查! 中分享您的想法。

欢迎!请参阅 关于 页面以获取更多有关这个平台的信息。

+1 votes

以下代码会崩溃

(defmacro m []
  `(fn [x]))

(macroexpand '(m))

错误是

对 clojure.core/fn 的调用不符合规范
[...]
:reason "Extra input"

以下代码不会崩溃并且按预期工作

(defmacro m []
  `(asdfasdf [x]))

(macroexpand '(m))

为什么第一个片段会崩溃,尽管宏只是应该返回代码片段?为什么它会运行 fn 呢?

2 答案

+3 votes

当选
 
最佳答案

不确定您使用的是哪个版本的 Clojure 或编辑器,但 Clojure 1.10.3 在标准 repl 中提供了更有用的输出

user=> (macroexpand '(m))
Syntax error macroexpanding clojure.core/fn at (REPL:1:1).
(user/x) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
user/x - failed: vector? at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list

如您所见,它运行的不是您的代码,而是在遇到问题时代码(递归地)进行宏扩展。第一次扩展将变成

(clojure.core/fn [user/x])

clojure.core/fn 是一个宏,在宏展开过程中,会检查 fn 宏是否与 fn 规范相符(这是宏的默认行为)。规范发现 user/x 并将其视为 “额外的输入” 在 :params 中,因为它不符合任何期望的参数形式(通常是未限定的符号,但也包括所有的递归解构形式)。实际上,这里的真正问题是 user/x 而不是 x

您可以使用以下代码来修复此问题

(defmacro m []
  `(fn [~'x]))

重新显示
非常感谢,这确实有道理。
我不明白为什么x评估为user/x,尽管它被语法引号括起来。这与取消引号然后再引用有什么不同呢?
语法引号在当前命名空间上下文中解析所有符号。取消引号打开计算,然后评估一个被引用且未解析的符号。
明白了,再次感谢。
+1 votes

试试 macroexpand-1 - 然后,错误应该会很明显。

...