Clojure 创建与其他记录类型相同的记录
我有一个例子,我想根据记录实例的类型创建一个新的记录实例,该记录实例作为参数与属性映射一起出现Clojure 创建与其他记录类型相同的记录,clojure,clojurescript,Clojure,Clojurescript,我有一个例子,我想根据记录实例的类型创建一个新的记录实例,该记录实例作为参数与属性映射一起出现 (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
(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
作为记录AST节点和children
新的键/值对(可能不包含节点
中的某些字段) 我以为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}
您也可以这样做:
(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的反射方法更有效还是效率更低。谢谢@amalloy的回答!我在中看到了
,但这有一个问题。我希望新的记录实例具有与映射完全相同的属性,但是into
将映射与记录实例合并。例如,使用(defrecord Bar[xy])
,(into(Bar.1){:y3})
将返回#user.Bar{:x1:y3}
,而不是#user.Bar{:xnil:y3}
。我的意思是,您所写的内容也不完全相同{:xnil:y3}
与{:y3}
是完全不同的映射。在最一般的情况下,您所要求的是不可能的,因为映射可以有任意数量的字段(包括零),而记录可以有非零数量的必填字段。你必须决定你最喜欢哪种折衷办法。@amalloy它看起来像,所以我认为这是一个错误。@amalloy你是对的,地图不完全一样。但是map->
构造函数假定所有字段都是可选的,这就是为什么要首先使用map调用。考虑到字段值的要求仅存在于位置构造函数中,并且我正在编写的代码假设映射已被传递,我可以接受这种折衷办法。@SamEstep我认为ClojureScript的记录为空。它有IEmptyableCollection
,这是调用empty
的接口。方法不错。如果clear
函数仅用于在记录上调用,那么您可以安全地放弃reducer中的类型相等检查,因为在记录上调用dissoc
总是返回一个映射。但是,对于我正在编写的代码,我不希望像您的示例那样使用nil
值字段。它应该是不存在的,而不是nil
@g7s,我不明白<记录上的code>dissoc
仅当被dissoc
ed的键是记录中所需的键之一时才返回映射;这正是我使用type
的原因。在你关于amalloy的回答中,你明确地说,他的进入
方法的问题在于它没有返回一条记录,该记录的条目为:x nil
。你能澄清一下你在这里的评论是什么意思吗?我错了dissoc
如果密钥在所需的密钥中,确实会返回一个映射。不要理会我对这件事的评论。关于我对amalloy回答的评论,我的意思是#user.Bar{:y 3}
(没有x
字段),但我意识到我晚了一点犯了一个错误,无法编辑我的评论:/@g7s你也错了;给定(defrecord Bar[xy])
,则不存在#user.Bar{:y 3}
这样的对象。你能得到的最接近的是#user.Bar{:x nil:y 3}
。好了,现在已经很晚了,我想我需要一些睡眠:P是的,正确的是#user.Bar{:x nil:y 3}
,你的函数给出了相同的结果。美好的真的,整个问题和你对它的所有评论让我不禁要问,“你为什么要使用记录?”听起来你希望记录的行为完全像地图一样,你要做所有这些工作来规避它们的核心功能。@amalloy是的,我使用记录作为AST的节点。你认为我最好使用带有类型字段的普通映射吗?在description@g7s我发现你编辑的例子非常混乱。在协议中使用记录的全部要点是使用Clojure的多态性特性,但这里您只是为映射实现协议(所有向量都是映射),然后显式地检查它是否是记录,并对所有记录类型使用相同的代码。您能演示一下为什么要使用记录吗?@SamEstep因为AST节点类型很多,所以对每种类型都实现IZipableTreeNode
非常困难