请在 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代替 - 然后,错误应该变得明显。

...