Clojure: как создать запись внутри функции? - PullRequest
1 голос
/ 16 февраля 2012

В ближайшем будущем я бы хотел создать запись внутри функции

Я пытался:

(defn foo []
  (defrecord MyRecord [a b])
  (let [b (MyRecord. "1" "2")]))

Но это вызывает исключение:

java.lang.IllegalArgumentException: Unable to resolve classname: MyRecord

Есть идеи?

1 Ответ

10 голосов
/ 16 февраля 2012

Ключевые моменты

Вы должны использовать 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 являются особенными, тем не менее, они действуют так, как если бы формы, которые они обертывают, обрабатывались индивидуально на верхнем уровне.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...