Удивительное поведение, связанное с записями, протоколами и компиляцией
Я столкнулся со мной, немного удивительным поведением, казалось бы,
связанных с clojure записями.
Настройка выполняется следующим образом:
-
Одно пространство имен определяет тип записи:
(ns defrecordissue.arecord)
(defrecord ARecord [])
-
Другое пространство имен определяет протокол и расширяет его до записи
тип, определенный в 1:
(ns defrecordissue.aprotocol
(:require [defrecordissue.arecord])
(:import [defrecordissue.arecord ARecord]))
(defprotocol AProtocol
(afn [this]))
(extend-protocol AProtocol
ARecord
(afn [this] 42))
-
Третье пространство имен создает экземпляр записи и вызывает
функция протокола в записи.
(ns defrecordissue.aot1
(:require [defrecordissue.aprotocol]
[defrecordissue.arecord]))
(defrecordissue.aprotocol/afn (defrecordissue.arecord/->ARecord))
Когда пространство имен 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
компиляция завершается успешно
во второй раз, поскольку класс для записи теперь доступен на
CLASSPATH. Поэтому я могу обойти эту проблему с помощью AOT-компиляции
пространство имен, определяющее тип записи.
Я создал простой проект leiningen на GitHub, который иллюстрирует
вопрос, см. README для использования:
https://github.com/ragnard/defrecordissue
Почему я вижу это поведение и как правильно его избежать?
UPDATE
Я добавил новую ветку в репозиторий GitHub, лучше иллюстрирующую основную проблему:
https://github.com/ragnard/defrecordissue/tree/more-realistic/
Проблема возникает независимо от того, где (т.е. в каком пространстве имен) запись
экземпляр построен.
Ответы
Ответ 1
Я могу воспроизвести проблему с вашим репо. Здесь три решения, которые работают для меня:
-
Сообщите lein compile
, чтобы скомпилировать другие пространства имен:
lein compile defrecordissue.aprotocol defrecordissue.arecord defrecordissue.aot1
-
Помещенный
:aot [defrecordissue.aprotocol defrecordissue.arecord defrecordissue.aot1]
в project.clj
.
-
Помещенный
:aot :all
в project.clj
.
Последние два делают lein compile
выполнение работы lein aot1
(в случае 2.) и оба lein aot1
и lein aot2
(в случае 3.).
Ответ 2
Я все время сталкиваюсь с этим. Это то, что я придумал, и это работает:
(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])))))
Затем вам нужно только изменить свой второй блок кода на:
(ns defrecordissue.aprotocol)
(defprotocol AProtocol
(afn [this]))
(extend-protocol* AProtocol
defrecordissue.arecord.ARecord
(afn [this] 42))
Возможно, это не самое чистое решение, но вам больше не нужно использовать AOT для своих библиотек.