clojure - 为什么一个从宏中得到的函数可以工作而另一个不能被编译?

标签 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)))

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

最佳答案

我假设您在没有引用的情况下定义了宏主体,并且您很好奇为什么它会导致如此奇怪的错误消息。如果你真的想定义一个宏来调用 (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
}

现在当你有以下 sexp 时:

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

我注意到 Clojure 阅读器评估 (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 邮件列表上查看。

关于clojure - 为什么一个从宏中得到的函数可以工作而另一个不能被编译?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38619552/

相关文章:

c - 有没有办法在 AVR gcc 中为引脚定义宏,以便我可以将它们作为变量访问?

c - 为单元测试编写复杂的预处理器宏

在 Clojure 向量图中对向量进行排序

Clojure 应用与映射

haskell - Haskell 有类似 Incanter 的东西吗?

c++ - 如何正确扩展这个宏?

c - 编译时的offsetof

clojure - 如何使用具有多个参数的函数的映射

Clojure 中的 Hadoop - 从哪里开始?

netbeans - 在 Netbeans 上激活文本编辑宏