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

欢迎!请在 关于 页面上了解有关此工作方式的更多信息。

+1 投票

以下代码会导致崩溃

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

(macroexpand '(m))

错误信息为

调用 clojure.core/fn 不符合规范
[...]
:原因 "额外输入"

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

(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 - 这样错误应该会明显。

...