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代替 - 那样错误应该很明显。

...