创建与另一个相同类型的记录

Create a record of the same type as another

我有一个案例,我想根据记录实例的类型创建一个新的记录实例,该记录实例作为参数与属性映射一起出现。

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

我现在有的是以下几行:

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

还有其他我看不到的更简单、更惯用的方法吗?

谢谢!

编辑

为了提供更多细节,我正在构建一个 AST,节点是记录,我正在使用拉链来访问并可能更改/删除 AST 的一部分。我有一个 IZipableTreeNode 协议

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

实现 IZipableTreeNode 的不同类型之间是 IPersistentMap

  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)))

当访问者说从节点中删除一个字段(或更改它)时,make-node 被调用,node 是记录 AST 节点,children 新的 key/value 对(可能不包含 node 中的某些字段)。

我以为 clojure.core/empty 曾经这样做过。也就是说,我认为

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

会return

#user.Foo{:x nil}

但现在肯定不会那样做:我不确定那是变了还是我记错了。我找不到一种超级干净的方法来做到这一点,但我至少有比你的方法更好的方法。您正在使用的 user/map->Foo 函数基于与 class、user.Foo/create 一起生成的静态方法,直接调用它有点 classier,通过反射.

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}

但是,我突然想到,您可能不需要执行任何这些操作!您一开始就认为实现 "build a new thing based on a previous thing" 目标的方法是从头开始,但为什么要这样做?只要传递给您的函数的记录没有添加任何 "extension" 字段(即,那些不是记录定义本身的一部分),那么您可以简单地使用 clojure.core/into:

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

你也可以这样做:

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

示例:

(defrecord Foo [x y])

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

我不确定这是否比@amalloy 的反射方法更有效或更不有效。