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

欢迎!有关如何操作的更多信息,请参阅 关于 页面。

+1 投票

以下代码导致崩溃

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

(macroexpand '(m))

错误信息为

clojure.core/fn 的调用未符合规范
[...]
:reason "额外的输入"

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

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

(macroexpand '(m))

第一个代码片段是如何崩溃的呢?当宏只是返回一个引用代码片段时,为什么会运行 fn

2 答案

+3 投票

已选择
 
最佳答案

不确定您使用的是哪个版本的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 投票
/>

请尝试使用 macroexpand-1,然后错误应该就会很清楚。

...