创建与另一个相同类型的记录
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 的反射方法更有效或更不有效。
我有一个案例,我想根据记录实例的类型创建一个新的记录实例,该记录实例作为参数与属性映射一起出现。
(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 的反射方法更有效或更不有效。