为什么一个函数从宏中运行而另一个函数无法编译?

时间:2016-07-27 17:25:19

标签: clojure macros

这是片段:

(defmacro produce-constantly-fn []
  (constantly :value))

(defmacro produce-fn []
  (fn [& args] :value))

(defn test-fn []
  ((produce-fn)))

;; during evaluation of form below it throws:
;; java.lang.IllegalArgumentException
;; No matching ctor found for class clojure.core$constantly$fn__4614
(defn test-constantly-fn []
  ((produce-constantly-fn)))

为什么上一个函数无法编译?该片段可以被视为某种排序宏滥用,但无论如何......

2 个答案:

答案 0 :(得分:5)

我假设您在没有引用的情况下定义了您的宏体,并且您很好奇为什么它会导致如此奇怪的错误消息。如果您真的打算定义一个用于调用(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的实现时,您会发现(docs和meta省略):

(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
}

现在当你跟随sexp:

(defn test-constantly-fn []
  ((produce-constantly-fn)))

我注意到Clojure读者逃避(produce-constantly-fn),它应该只返回一个函数对象(通过调用(constantly :value)生成)但我在调试器中发现它生成了clojure.core$constantly$fn__4614.符号(注意{ {1}}在符号的末尾 - 它是一个用于调用构造函数的Java互操作表单。看起来函数对象/值以某种方式转换为表示其构造函数调用的符号。我发现函数值被转换为.对象,其中包含对已编译的类名的引用,这可能以某种方式转换为符号。

读者试图进一步解决Compiler$InvokeExpr。它被读者转换为对clojure.core$constantly$fn__4614.类构造函数的调用,没有参数。

如上所述,该类的构造函数只需要一个构造函数,因此编译失败(在clojure.core$constantly$fn__4614clojure.core$constantly$fn__4614构造函数体中):

clojure.lang.Compiler.NewExpr

我不确定为什么Clojure读者将函数值转换为带有构造函数调用互操作形式的符号并导致此类行为因此我只提供了错误消息的直接原因而不是导致代码无效的根本原因。我想这可能是一个错误或者是一个有意识的设计决定。从宏作者那里最好快速失败并了解宏的返回值不是有效的代码数据,但另一方面,确定返回的数据是否是有效代码可能非常困难或不可能。值得查看Clojure邮件列表。

答案 1 :(得分:0)

我认为发生的事情是:宏在功能之前解析。

因此,如果您在函数上调用macroexpand-1

(def test-constantly-fn (clojure.core/fn ([] ((produce-constantly-fn)))))

因此在函数之前调用((produce-constantly-fn))并给出列出的错误。