2024 年 Clojure 调查问卷 中分享您的想法!

欢迎!请查看 关于 页面,了解更多关于这里是如何工作的信息。

+1 投票

以下代码崩溃

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

(macroexpand '(m))

错误是

clojure.core/fn 的调用不符合规范
[...]
:reason "多余输入"

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

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

(macroexpand '(m))

为什么第一个代码片段会崩溃,而宏只是应该返回一个引用的代码片段吗?为什么会运行 fn 呢?

2 答案

+3 投票
by
selected by
 
最佳答案

不确定你使用的是哪个版本的 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 规范(这是默认开启的宏规范)。规范发现 user/x 并将其视为 ":params" 中的“多余输入”,因为它们不符合任何预期的参数形式(通常是不带限定符的符号,但也包括所有递归解构形式)。实际上,这里的问题是 user/x 而不是 x

你可以通过以下方式修复这个问题

(defmacro m []
  `(fn [~'x]))
by
重新显示 by
非常感谢,这确实很有道理。
by
我不明白为什么x在语法引用的情况下被评估为user/x。这与去引用后再引用有什么区别呢?
by
语法引用将所有符号根据当前命名空间环境进行解析。消引用后开启求值,然后评估引用中未解析的符号。
by
明白了,再次感谢。
+1 投票
by

尝试使用macroexpand-1代替 - 这样错误应该就会很明显了。

...