Alex Rivera | Logout

Clojure: Assigning defrecord fields from Map

Asked 2010-12-23T15:55:53.107
10

Following up on How to make a record from a sequence of values, how can you write a defrecord constructor call and assign the fields from a Map, leaving un-named fields nil?

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

I imagine a macro could be written to do this.

Edit
Report

1 Answer

12

You can simply merge the map into a record initialised with nils:

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

Note that records are capable of holding values stored under extra keys in a map-like fashion.

The list of a record's fields can be obtained using reflection:

(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"})))

The latter function returns a seq of strings:

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

__meta and __extmap are the fields used by Clojure records to hold metadata and to support the map functionality, respectively.

You could write something like

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

and use it to create empty instances of record classes like so:

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

The fully qualified name is essential here. It's going to work as long as the record class has been declared by the time any empty-record forms referring to it are compiled.

If empty-record was written as a function instead, one could have it expect an actual class as an argument (avoiding the "fully qualified" problem -- you could name your class in whichever way is convenient in a given context), though at the cost of doing the reflection at runtime.

answered 2010-12-23T17:06:28.327

Your Answer