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,然后错误应该会更加明显。

...