我假设您在没有引用的情况下定义了宏主体,并且您很好奇为什么它会导致如此奇怪的错误消息。如果你真的想定义一个宏来调用(constantly :value),那么你应该使用引用,它会起作用:
(defmacro produce-constantly-fn []
`(constantly :value))
(defn test-constantly-fn []
((produce-constantly-fn)))
=> #'user/test-constantly-fn
(test-constantly-fn)
=> :value
现在回到你的案例而不引用。它看起来非常有趣和神秘,所以我做了一些挖掘。以下是我的发现:
定义宏时:
(defmacro produce-constantly-fn []
(constantly :value))
它只会创建一个名为 produce-constantly-fn 的函数并将其标记为宏(它仍然是 Clojure 函数)。
当您查看constantly 的实现时,您会发现(省略了文档和元数据):
(defn constantly [x]
(fn [& args] x))
在底层,它将编译为一个闭包对象,该对象将实现IFn,并将有一个构造函数参数来关闭x参数。在 Java 代码中是这样的:
public class clojure.core$constantly$fn__4614 {
private final Object x;
public clojure.core$constantly$fn__4614(Object x) {
this.x = x;
}
public Object invoke(...) {
return x;
}
// other invoke arities
}
现在当你有以下性行为时:
(defn test-constantly-fn []
((produce-constantly-fn)))
我注意到 Clojure 阅读器 evals (produce-constantly-fn) 应该只返回一个函数对象(通过调用 (constantly :value) 生成),但我在调试器中发现它会生成 clojure.core$constantly$fn__4614. 符号而不是(注意末尾的 .符号 - 它是一种用于调用构造函数的 Java 互操作形式)。看起来函数对象/值以某种方式转换为表示其构造函数调用的符号。我可以发现函数值被转换为 Compiler$InvokeExpr 对象,其中包含对已编译类名的引用,该类名可能以某种方式转换为符号。
读者试图进一步解析clojure.core$constantly$fn__4614.。它被读者转化为对clojure.core$constantly$fn__4614clojure.core$constantly$fn__4614类构造函数的调用,没有参数。
正如您在上面看到的那样,该类的构造函数只需要一个构造函数,因此编译失败(在clojure.lang.Compiler.NewExpr 构造函数主体中):
java.lang.IllegalArgumentException: No matching ctor found for class clojure.core$constantly$fn__4614
我不确定为什么 Clojure 阅读器将函数值转换为具有构造函数调用互操作形式的符号并导致这种行为,因此我只介绍了错误消息的直接原因,而不是您的代码不起作用的根本原因.我想这可能是一个错误,或者是一个有意识的设计决定。从宏作者那里,最好快速失败并了解宏的返回值不是有效的代码数据,但另一方面,确定返回的数据是否是有效的代码可能非常困难或不可能.值得查看 Clojure 邮件列表。