与记录,协议和编译相关的令人惊讶的行为

时间:2013-04-17 09:21:56

标签: clojure

我遇到了一个看似微不足道的行为 与clojure记录有关。

设置如下:

  1. 一个名称空间定义了一种记录类型:

    (ns defrecordissue.arecord)
    
    (defrecord ARecord [])
    
  2. 另一个命名空间定义协议,并将其扩展到记录 在1:

    中定义的类型
    (ns defrecordissue.aprotocol
      (:require [defrecordissue.arecord])
      (:import [defrecordissue.arecord ARecord]))
    
    (defprotocol AProtocol
      (afn [this]))
    
    (extend-protocol AProtocol
      ARecord
      (afn [this] 42))
    
  3. 第三个命名空间构造记录的实例并调用 记录上的协议功能。

    (ns defrecordissue.aot1
      (:require [defrecordissue.aprotocol]
                [defrecordissue.arecord]))
    
    (defrecordissue.aprotocol/afn (defrecordissue.arecord/->ARecord))
    
  4. 编译defrecordissue.aot1命名空间时,在我的情况下使用 lein compile defrecordissue.aot1,编译失败了 以下例外:

    Exception in thread "main" java.lang.IllegalArgumentException: No implementation of method: :afn of protocol: #'defrecordissue.aprotocol/AProtocol found for class: defrecordissue.arecord.ARecord, compiling:(aot1.clj:5:1)
        at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3463)
        at clojure.lang.Compiler.compile1(Compiler.java:7153)
        at clojure.lang.Compiler.compile(Compiler.java:7219)
        at clojure.lang.RT.compile(RT.java:398)
        at clojure.lang.RT.load(RT.java:438)
        at clojure.lang.RT.load(RT.java:411)
        at clojure.core$load$fn__5018.invoke(core.clj:5530)
        at clojure.core$load.doInvoke(core.clj:5529)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invoke(core.clj:5336)
        at clojure.core$compile$fn__5023.invoke(core.clj:5541)
        at clojure.core$compile.invoke(core.clj:5540)
        at user$eval7.invoke(NO_SOURCE_FILE:1)
        at clojure.lang.Compiler.eval(Compiler.java:6619)
        at clojure.lang.Compiler.eval(Compiler.java:6609)
        at clojure.lang.Compiler.eval(Compiler.java:6582)
        at clojure.core$eval.invoke(core.clj:2852)
        at clojure.main$eval_opt.invoke(main.clj:308)
        at clojure.main$initialize.invoke(main.clj:327)
        at clojure.main$null_opt.invoke(main.clj:362)
        at clojure.main$main.doInvoke(main.clj:440)
        at clojure.lang.RestFn.invoke(RestFn.java:421)
        at clojure.lang.Var.invoke(Var.java:419)
        at clojure.lang.AFn.applyToHelper(AFn.java:163)
        at clojure.lang.Var.applyTo(Var.java:532)
        at clojure.main.main(main.java:37)
    Caused by: java.lang.IllegalArgumentException: No implementation of method: :afn of protocol: #'defrecordissue.aprotocol/AProtocol found for class: defrecordissue.arecord.ARecord
        at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:541)
        at defrecordissue.aprotocol$fn__40$G__35__45.invoke(aprotocol.clj:5)
        at clojure.lang.AFn.applyToHelper(AFn.java:161)
        at clojure.lang.AFn.applyTo(AFn.java:151)
        at clojure.lang.Compiler$InvokeExpr.eval(Compiler.java:3458)
        ... 25 more 
    

    如果我改变3)直接构造记录类,如下:

    (ns defrecordissue.aot2
      (:require [defrecordissue.aprotocol]
                [defrecordissue.arecord]))
    
    (defrecordissue.aprotocol/afn (defrecordissue.arecord.ARecord.))
    

    编译成功。

    我怀疑这与某种程度上有关 http://dev.clojure.org/jira/browse/CLJ-371,但我不明白 到底发生了什么。

    我还应该补充说,如果没有lein clean,编译就会成功 第二次,因为现在有一个记录课程 类路径。因此,我可以通过AOT编译解决这个问题 定义记录类型的命名空间。

    我在GitHub上创建了一个简单的leiningen项目来说明 问题,请参阅README以了解用法: https://github.com/ragnard/defrecordissue

    为什么我会看到这种行为,以及避免它的正确方法是什么?

    更新

    我在GitHub仓库中添加了一个新分支,更好地说明了核心问题: https://github.com/ragnard/defrecordissue/tree/more-realistic/

    无论记录在哪里(即在哪个命名空间中),都会出现问题 实例是构建的。

2 个答案:

答案 0 :(得分:2)

我可以用您的回购再现问题。以下是三个适合我的解决方案:

  1. 告诉lein compile编译更多名称空间:

    lein compile defrecordissue.aprotocol defrecordissue.arecord defrecordissue.aot1
    
  2. :aot [defrecordissue.aprotocol defrecordissue.arecord defrecordissue.aot1]
    

    project.clj

  3. :aot :all
    

    project.clj

  4. 后两者使lein compile完成lein aot1(在2的情况下)以及lein aot1lein aot2的工作(在3的情况下)

答案 1 :(得分:0)

我一直都遇到这个问题。这就是我想出来的,它似乎有效:

(defmacro with-datatype
  [datatype & body]
  (let [last-dot (.lastIndexOf ^String (str datatype) ".")
        ns (-> datatype
               str
               (subs 0 last-dot)
               symbol)]
     `(do
        (require (quote ~ns))
        (import ~datatype)
        ~@body)))

(defmacro extend-type*
  [datatype & extensions]
  `(with-datatype ~datatype
     (extend-type ~datatype
       ~@extensions)))

(defmacro extend-protocol*
  [protocol & specs]
  (let [splitter (let [l (atom nil)]
                   #(if (symbol? %) (reset! l %) @l))
        specs (partition-by splitter specs)]
    `(do
       ~@(for [[datatype & extensions] specs]
           `(extend-type* ~datatype ~protocol ~@extensions)))))

然后您只需将第二个代码块更改为:

(ns defrecordissue.aprotocol)

(defprotocol AProtocol
  (afn [this]))

(extend-protocol* AProtocol
  defrecordissue.arecord.ARecord
  (afn [this] 42))

也许不是最干净的解决方案,但您不必再对您的图书馆进行AOT。