【问题标题】:Why one function got from a macro works while another cannot be compiled?为什么从宏中获得的一个函数可以工作,而另一个函数不能编译?
【发布时间】:2016-07-27 17:25:19
【问题描述】:

这里是sn-p:

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

为什么最后一个函数不能编译? sn-p 可以被认为是某种宏滥用,但无论如何......

【问题讨论】:

    标签: clojure macros


    【解决方案1】:

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

    【讨论】:

    • 非常感谢您的调查。谢谢!你的假设也是正确的:我故意在宏定义中省略了反引号。
    • 不客气。它看起来很有趣,值得一试。
    • 顺便说一句,你用什么调试器做的检查?
    • 我用过草书。我已经开始 REPL 调试会话并在 LispReaderCompiler 类中添加断点。
    • 辛苦了,@PiotrekBzdyl - 以及一些启发性的观察。但是,我不会认为它是 Clojure 错误。宏调用应该返回一个 sexp,而不是函数,所以在这种情况下,我们遇到了未定义的行为。也许更好的错误消息是最好的希望,但我怀疑这是否值得。
    【解决方案2】:

    我认为正在发生的事情是:宏在函数之前解析。

    所以如果你在你得到的函数上调用macroexpand-1

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-10
      • 2021-08-02
      • 1970-01-01
      • 2011-02-03
      • 1970-01-01
      相关资源
      最近更新 更多