Ключевые моменты
Вы должны использовать defrecord
только на верхнем уровне. 1
Итак, если вам нужен пользовательский тип записи, вы должны определить его вне foo
(в какой-то момент в вашем коде, который обрабатывается до определения foo
).
В противном случае вы могли бы просто использовать обычную карту. В частности, если foo
будет создавать объекты нескольких «типов» (на концептуальном уровне), вероятно, нет смысла пытаться создать тип записи (класс Java) для каждого; естественным решением было бы использовать карты, содержащие клавишу :type
, чтобы указать вид представляемой сущности.
Почему это не работает
Код из вопроса не компилируется, потому что компилятор Clojure разрешает имена классов, упомянутые в качестве первых аргументов, формам new
во время компиляции. ((MyRecord. "1" "2")
расширяется до (new MyRecord "1" "2")
во время процесса расширения макроса.) Здесь имя MyRecord
еще не может быть преобразовано в соответствующий класс, потому что последний еще не был определен (он будет создан defrecord
форма после foo
была впервые вызвана).
Чтобы обойти это, вы могли бы сделать что-то ужасное, как
(defn foo []
(eval '(defrecord MyRecord [x y]))
(let [b (clojure.lang.Reflector/invokeConstructor
;; assuming MyRecord will get created in the user ns:
(Class/forName "user.MyRecord")
(into-array Object ["1" "2"]))]
b))
, который заставляет котенка вызывать свой метод finalize
посредством отражения, что приводит к ужасной смерти.
Как последнее замечание, вышеуказанная ужасная версия будет работать, но она также создаст новый тип записи под тем же именем при каждом вызове. Это приводит к странностям. Попробуйте следующие примеры для вкуса:
(defprotocol PFoo (-foo [this]))
(defrecord Foo [x]
PFoo
(-foo [this] :foo))
(def f1 (Foo. 1))
(defrecord Foo [x])
(extend-protocol PFoo
Foo
(-foo [this] :bar))
(def f2 (Foo. 2))
(defrecord Foo [x])
(def f3 (Foo. 3))
(-foo f1)
; => :foo
(-foo f2)
; => :bar
(-foo f3)
; breaks
;; and of course
(identical? (class f1) (class f2))
; => false
(instance? (class f1) f2)
; => false
В этот момент (Class/forName "user.Foo")
(при условии, что все это происходит в пространстве имен user
) возвращает класс f3
, из которых f1
и f2
не являются экземплярами.
1 Макросы иногда могут выводить форму defrecord
, завернутую в do
вместе с некоторыми другими формами; верхние уровни do
являются особенными, тем не менее, они действуют так, как если бы формы, которые они обертывают, обрабатывались индивидуально на верхнем уровне.