2013-04-17 3 views
11

私には、やや驚くべき振る舞いが見受けられました。一見、 がクロージャレコードに関連しています。次のようにレコード、プロトコル、コンパイルに関連する驚くべき振る舞い

設定は次のとおり

  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. 第3の名前空間レコードのインスタンスを構築し、レコード上のプロトコル関数 を呼び出します。

    (ns defrecordissue.aot1 
        (:require [defrecordissue.aprotocol] 
          [defrecordissue.arecord])) 
    
    (defrecordissue.aprotocol/afn (defrecordissue.arecord/->ARecord)) 
    

defrecordissue.aot1名前空間が lein compile defrecordissue.aot1を使用して、私の場合には、コンパイルされた場合、コンパイルが 次の例外で失敗します。私は3を変更した場合

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 

)レコードを構築します直接のように、クラス:

(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

は、なぜ私はこの振る舞いを見ていて、それを回避するための正しい方法は何ですか?

UPDATE

私はより良いコアの問題を示すGitHubのレポに新しいブランチを追加しました: https://github.com/ragnard/defrecordissue/tree/more-realistic/

問題は関係なく、どこの発生記録 インスタンスである(つまり、その名前空間に。)構築された。

+0

いい考えですが、同じ例外です。私は 'defrecordissue.arecord'が最初にロードされると思います。なぜなら、' defrecordissue.aprotocol'の中でも必要であるからです。 – Ragge

+0

上記のコメントは明らかに削除された別のコメントに関連していました。 – Ragge

答えて

2

私はあなたのレポで問題を再現できます。

  1. は、より多くの名前空間をコンパイルするlein compile教える::project.clj

    :aot [defrecordissue.aprotocol defrecordissue.arecord defrecordissue.aot1] 
    

    を入れ

    lein compile defrecordissue.aprotocol defrecordissue.arecord defrecordissue.aot1 
    
  2. ここでは私のために働く3つのソリューションをです。

  3. project.clj

    :aot :all 
    

    を入れてください。

後者の二つはlein compileが(2の場合)lein aot1の仕事を行い、lein aot1lein aot2(3の場合)の両方。

+0

あなたの答えをありがとう。私はAOT によって 'defrecordissue.arecord'名前空間をコンパイルすることで問題を回避できることを知っています。ただし、問題が発生した場合は、これは完全に満足できるものではありません。 その場合、 'defrecordissue.arecord'と ' defrecordissue.aprotocol'はライブラリの一部であり、 'defrecordissue.aot1'はそのライブラリのコンシューマに過ぎず、 はレコードタイプについて知りませんプロトコルも)。 では、このような例題のようにレコード型を直接インスタンス化しません。 が可能ならば、AOTコンパイルされたクラスなしでライブラリを提供したいと思います。 – Ragge

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) 
     [email protected]))) 

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

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

次に、あなただけにあなたの2番目のコードブロックを変更する必要があります。

たぶん
(ns defrecordissue.aprotocol) 

(defprotocol AProtocol 
    (afn [this])) 

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

ないクリーンなソリューションが、あなたドンあなたの図書館をもうAOTにする必要はありません。