Clojure: назначение полей дезаписи из карты

Следуя Как сделать запись из последовательности значений, как вы можете написать вызов конструктора defrecord и назначить поля из Map, оставив безымянные поля nil?

(defrecord MyRecord [f1 f2 f3])
(assign-from-map MyRecord {:f1 "Huey" :f2 "Dewey"})  ; returns a new MyRecord

Я предполагаю, что для этого можно написать макрос.


person Ralph    schedule 23.12.2010    source источник
comment
Следует отметить, что начиная с clojure 1.3.0 вы можете делать (map->MyRecord {:f1 "Huey", :f2 "Dewey"}) или #user.MyRecord{:f1 "Huey", :f2 "Dewey"}   -  person Claude    schedule 27.03.2012


Ответы (3)


Вы можете просто merge преобразовать карту в запись, инициализированную с помощью nils:

(merge (MyRecord. nil nil nil) {:f1 "Huey" :f2 "Dewey"})

Обратите внимание, что записи могут хранить значения, хранящиеся под дополнительными ключами, в виде карты.

Список полей записи можно получить с помощью отражения:

(defn static? [field]
  (java.lang.reflect.Modifier/isStatic
   (.getModifiers field)))

(defn get-record-field-names [record]
  (->> record
       .getDeclaredFields
       (remove static?)
       (map #(.getName %))
       (remove #{"__meta" "__extmap"})))

Последняя функция возвращает последовательность строк:

user> (get-record-field-names MyRecord)
("f1" "f2" "f3")

__meta и __extmap — это поля, используемые записями Clojure для хранения метаданных и поддержки функций карты соответственно.

Вы можете написать что-то вроде

(defmacro empty-record [record]
  (let [klass (Class/forName (name record))
        field-count (count (get-record-field-names klass))]
    `(new ~klass ~@(repeat field-count nil))))

и используйте его для создания пустых экземпляров классов записей, например:

user> (empty-record user.MyRecord)
#:user.MyRecord{:f1 nil, :f2 nil, :f3 nil}

Здесь необходимо полное имя. Это будет работать до тех пор, пока класс записи будет объявлен к моменту компиляции любых empty-record форм, ссылающихся на него.

Если бы вместо этого empty-record была написана как функция, можно было бы ожидать, что в качестве аргумента будет фактический класс (избегая проблемы «полного уточнения» — вы можете назвать свой класс любым способом, удобным в данном контексте), хотя и ценой выполнения отражения во время выполнения.

person Michał Marczyk    schedule 23.12.2010
comment
Когда запись реализует протокол, в запись добавляются некоторые другие поля. Вы можете заменить последний remove вызов get-record-field-names на (remove #(re-find #"__meta|__extmap|__cached" %)))) - person Nicolas Buduroi; 14.06.2011

Сегодня Clojure генерирует функцию map->RecordType при определении записи.

(defrecord Person [first-name last-name])
(def p1 (map->Person {:first-name "Rich" :last-name "Hickey"}))

Карта не требуется для определения всех полей в определении записи, и в этом случае отсутствующие ключи имеют нулевое значение в результате. Карта также может содержать дополнительные поля, не являющиеся частью определения записи.

person Qrt    schedule 08.11.2015

Как упоминалось в связанных ответах на вопросы, код здесь показывает, как создать макрос defrecord2 для создания конструктор, который принимает карту, как показано здесь. Особый интерес представляет макрос make-record-constructor. .

person Alex Miller    schedule 23.12.2010