2013-04-17 12 views
11

Ho riscontrato un comportamento, a me leggermente sorprendente, apparentemente correlato ai record del clandestino.Comportamento sorprendente relativo a record, protocolli e compilazione

La configurazione è la seguente:

  1. Uno namespace definisce un tipo di record:

    (ns defrecordissue.arecord) 
    
    (defrecord ARecord []) 
    
  2. altro namespace definisce un protocollo, e si estende al tipo di record definito in 1:

    (ns defrecordissue.aprotocol 
        (:require [defrecordissue.arecord]) 
        (:import [defrecordissue.arecord ARecord])) 
    
    (defprotocol AProtocol 
        (afn [this])) 
    
    (extend-protocol AProtocol 
        ARecord 
        (afn [this] 42)) 
    
  3. Un terzo spazio dei nomi costruisce un'istanza del record e invoca la funzione di protocollo nel record.

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

Quando lo spazio dei nomi defrecordissue.aot1 viene compilato, nel mio caso usando lein compile defrecordissue.aot1, la compilazione non riesce con il seguente eccezione:

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 

se cambio 3) per costruire il record classe direttamente, in questo modo:

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

(defrecordissue.aprotocol/afn (defrecordissue.arecord.ARecord.)) 

La compilazione ha esito positivo.

Il mio sospetto è che questo è in qualche modo correlato a http://dev.clojure.org/jira/browse/CLJ-371, ma non capisco esattamente quello che sta accadendo.

Dovrei anche aggiungere che senza la lein clean, la compilazione riesce la seconda volta, dal momento che una classe per il record è ora disponibile sul classpath. Pertanto, posso ovviare a questo problema con AOT-compilando lo lo spazio dei nomi che definisce il tipo di record.

Ho creato un progetto semplice Leiningen su GitHub che illustra il problema , vedere README per l'uso: https://github.com/ragnard/defrecordissue

Perché vedo questo comportamento, e qual è il modo corretto per evitarlo?

UPDATE

ho aggiunto un nuovo ramo alle repo GitHub meglio illustrare il problema centrale: https://github.com/ragnard/defrecordissue/tree/more-realistic/

Il problema si verifica indipendentemente da dove (cioè in cui namespace.) Il record istanza è costruito.

+0

Buona idea, stessa eccezione però. Penso che 'defrecordissue.arecord' verrà prima caricato in ogni caso, poiché è anche richiesto in' defrecordissue.aprotocol' – Ragge

+0

Il commento sopra era correlato ad un altro commento apparentemente cancellato. – Ragge

risposta

2

Posso riprodurre il problema con il repository.Ecco tre soluzioni che lavorano per me:

  1. Dillo lein compile per compilare più spazi dei nomi:

    lein compile defrecordissue.aprotocol defrecordissue.arecord defrecordissue.aot1 
    
  2. Mettere

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

    in project.clj.

  3. Mettere

    :aot :all 
    

    in project.clj.

Quest'ultimo due fanno lein compile fare il lavoro di lein aot1 (nel caso di 2.) ed entrambi lein aot1 e lein aot2 (nel caso di 3.).

+0

Grazie per la risposta. So che posso risolvere il problema con AOT compilando lo spazio dei nomi 'defrecordissue.arecord'. Tuttavia, questo non è del tutto soddisfacente nel caso in cui il problema è nato. Nel caso , sia "defrecordissue.arecord' che ' defrecordissue.aprotocol' fanno parte di una libreria e 'defrecordissue.aot1' è solo un utente di quella libreria, e non conosce il tipo di record (o anche il protocollo). Non è che istanzia direttamente i tipi di record, come in questo esempio forzato. Mi piacerebbe consegnare la libreria senza alcuna classe compilata AOT se possibile. – Ragge

0

Mi capita sempre. Questo è ciò che mi è venuta in mente e sembra funzionare:

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

Poi solo bisogno di cambiare il secondo blocco di codice da:

(ns defrecordissue.aprotocol) 

(defprotocol AProtocol 
    (afn [this])) 

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

Forse non la soluzione più pulita, ma si don Devo più AOT le tue biblioteche.

Problemi correlati