我有一个案例,我希望根据作为参数的记录实例的类型以及属性映射创建记录的新实例。
(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
是新的键/值对({1}}可能不包含node
中的某些字段。
答案 0 :(得分:5)
我以为clojure.core/empty
曾经这样做过。也就是说,我想
(defrecord Foo [x])
(empty (Foo. 1))
将返回
#user.Foo{:x nil}
但它现在肯定不会这样做:我不确定这是改变了还是我误解了。我找不到一个超级干净的方法来做到这一点,但我做的至少比你的方法更好。您正在使用的user/map->Foo
函数基于与类user.Foo/create
一起生成的静态方法,并且通过反射直接调用它有点类比。
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}
但是,现在你可能不需要做任何这个!你开始怀疑的是,实现“基于以前的事物构建新事物”的目标的方法是从头开始,但为什么这样做呢?只要传入您的函数的记录没有添加任何“扩展”字段(即那些不属于记录定义本身的那些),那么您只需使用clojure.core/into
:
(into (Foo. 1) {:x 5}) ;=> #user.Foo{:x 5}
答案 1 :(得分:3)
你也可以这样做:
(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的反思方法更有效或更低效。