2017-08-01 79 views
3

我有,我想创建基于作为参数连同地图的属性来记录实例的类型记录的新实例的情况下。创建同一类型的记录作为另一个

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

实现了不同类型之间的IZipableTreeNodeIPersistentMap

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新的键/值对(可能不包含node中的某些字段)。

+1

真的这整个问题和你对它的所有评论让我问:“你为什么使用记录?”这听起来像是你希望记录的表现完全像地图一样,并且你要完成所有这些工作来绕过它们的核心功能。 – amalloy

+0

@amalloy是我使用记录作为AST的节点。你会不会更喜欢用带有_type_字段的简单地图呢? – g7s

+0

在说明中添加了一些更多详细信息 – g7s

回答

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} 
+0

感谢@amalloy的答案!我已经看到了“进入”,但这有一个问题。我希望新的记录实例拥有与地图相同的_exact_属性,但是'into'将地图与记录实例合并。所以例如有'(defrecord Bar [xy])',(进入(Bar。1){:y 3})''将返回'#user.Bar {:x 1:y 3}'而不是'#user .Bar {:x nil:y 3}'。 – g7s

+0

我的意思是,你写的东西并不完全相同。 “{:x nil:y 3}”与“{:y 3}”是完全不同的地图。在最常见的情况下,你所要求的是不可能的,因为地图可以具有任何数量的字段(包括零),并且记录可以具有非零数量的必需字段。你必须决定你最适合的妥协方式。 – amalloy

+0

@amalloy它看起来像[ClojureScript记录实现'空'](https://groups.google.com/forum/#!topic/clojure/0bM3nLj18SM)和[Clojure假定记录实现'空'](https:/ /dev.clojure.org/jira/browse/CLJ-1975),所以我会说这是一个错误。 –

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的反射方法效率较低。

+0

不错的做法。如果'clear'函数只能在记录上调用,那么你可以放心地减少reducer中的类型相等性检查,因为调用记录上的'dissoc'总是返回一个映射。对于我正在编写的代码,我不想像你的例子那样使用'nil'赋值域。它应该不存在而不是'nil'。 – g7s

+1

@ g7s我不明白。如果记录中的“dissoc”键是记录中必需的键之一,则记录上的“dissoc”仅返回映射;这正是我使用'type'的原因。并在你的[评论](https://stackoverflow.com/questions/45443819/create-a-record-of-the-same-type-as-another#comment77851137_45444267)amalloy的答案,你具体说,问题与他的“进入”方法是,它没有返回记录的条目“:x nil”。你能否在这里澄清你的意见? –

+0

我错了'dissoc'确实会返回一个映射,如果这个键在所需的键中。不理我对此的评论。关于我对amalloy答案的评论我的意思是'#user。酒吧{:y 3}'(没有'x'字段),但意识到我犯了一个错误迟到,无法编辑我的评论:/ – g7s

相关问题