Следуя Как сделать запись из последовательности значений, как вы можете написать вызов конструктора defrecord и назначить поля из Map, оставив безымянные поля nil?
(defrecord MyRecord [f1 f2 f3])
(assign-from-map MyRecord {:f1 "Huey" :f2 "Dewey"}) ; returns a new MyRecord
Я предполагаю, что для этого можно написать макрос.
Ответ 1 · рейтинг 12 · Michał Marczyk
Вы можете просто 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 была написана как функция, можно было бы ожидать, что в качестве аргумента будет фактический класс (избегая проблемы "полного уточнения" - вы можете назвать свой класс любым способом, удобным в данном контексте), хотя и ценой выполнения отражения во время выполнения.
Ответ 2 · рейтинг 4 · Qrt
Сегодня Clojure генерирует функцию map->RecordType при определении записи.
(defrecord Person [first-name last-name])
(def p1 (map->Person {:first-name "Rich" :last-name "Hickey"}))
Карта не требуется для определения всех полей в определении записи, и в этом случае отсутствующие ключи имеют нулевое значение в результате. Карта также может содержать дополнительные поля, не являющиеся частью определения записи.