2017-08-01 77 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

相關問題