Create a record of the same type as the other

I have a case where I would like to create a new record instance based on the type of the record instance that comes as an argument along with the attribute map.

(defn record-from-instance
  [other attrs]
  ;; Code that creates the new record based on "other"
  )

      

What I have now is something among the lines:

(defn record-from-instance
  [other attrs]
  (let [matched (s/split (subs (str (class other)) 6) #"\.")
        path (s/join "." (pop matched))
        class-name (peek matched)]
    ((resolve (symbol (str path "/" "map->" class-name))) attrs)))

      

Is there another easier idiomatic way to do this that I can't see?

Thank!

EDIT

To give more details, I am creating an AST with nodes being entries, and I am using lightning to visit and possibly modify / delete parts of the AST. I have a protocolIZipableTreeNode

(defprotocol IZipableTreeNode
  (branch? [node])
  (children [node])
  (make-node [node children]))

      

Between the different types that implement IZipableTreeNode

isIPersistentMap

  IPersistentMap
  (branch? [node] true)
  (children [node] (seq node))
  (make-node [node children]
    (let [hmap (into {} (filter #(= (count %) 2)) children)]
      (if (record? node)
        (record/from-instance node hmap)
         hmap)))

      

When the user says they are removing a field from a node (or modifying it), they make-node

retrieve the node

node's AST record and children

new key / value pairs (which may not contain some of the fields in node

).

+3


source to share


2 answers


I thought it was used for this clojure.core/empty

. That is, I thought

(defrecord Foo [x]) 
(empty (Foo. 1))

      

will return

#user.Foo{:x nil}

      



But that certainly doesn't do it now: I'm not sure if it changed, or if I forgot. I can't seem to find a super clean way to do this, but I have something better than your approach. The function used is user/map->Foo

based on a static method generated along with the class, user.Foo/create

and it is somewhat cooler to call this directly instead via reflection.

user> ((fn [r attrs]
         (.invoke (.getMethod (class r) "create" 
                              (into-array [clojure.lang.IPersistentMap]))
                  nil, (into-array Object [attrs])))
       (Foo. 1) {:x 5})
#user.Foo{:x 5}

      

However, this is happening to me, now you may not need to do anything! You started out with the prejudice that the path to achieving your goal of "building a new thing from the previous thing" has to start from scratch, but why do that? As long as the record passed to your function doesn't add any extra fields to it (i.e., those that are not part of the record definition itself), you can simply use clojure.core/into

:

(into (Foo. 1) {:x 5}) ;=> #user.Foo{:x 5}

      

+5


source


You can also do this:

(defn clear [record]
  (reduce (fn [record k]
            (let [without (dissoc record k)]
              (if (= (type record) (type without))
                without
                (assoc record k nil))))
          record
          (keys record)))

(defn map->record [record m]
  (into (clear record) m))

      

Example:



(defrecord Foo [x y])

(map->record (map->Foo {:x 1 :y 2 :z 3}) {:y 4})
;;=> #example.core.Foo{:x nil, :y 4} 

      

I'm not sure if this would be more efficient or less efficient than @amalloy's approach.

+3


source







All Articles