宏读取字段名称并设置为其他对象的值

时间:2016-09-28 14:32:30

标签: clojure macros clojure-java-interop

我是clojure的新手,我正在尝试重构一些我写的看起来像这样的代码

(defn transform
  [entity]
  (let [new-obj (doto (SomeObj.)
                (.setField1 (:field-1 entity)))
  new-obj))

我有很多需要实现此功能的对象,但我想创建一个接受entityprototype Ex:(SomeObj.)map的宏的宏keys是原型的字段名称,值是关键字的向量,用于从entity获取正确的字段。对于每个key,我需要使用(get-in map [value as a vector])中的参数调用.set + keyName。

我希望每个新实体都可以创建映射配置,只为特殊情况编写代码。这可能是使用宏吗?

或者是否有更惯用的方式在clojure中执行此操作。

1 个答案:

答案 0 :(得分:1)

是的,您可以轻松地使用这样的宏:

(defmacro map-to [type mappings entity]
  `(doto (new ~type)
     ~@(map (fn [[field entity-field]]
              `(~(symbol (str ".set" (clojure.string/capitalize field)))
                (~entity-field ~entity)))
            mappings)))

这将生成您需要的代码:

(map-to java.util.Date {date :dt minutes :mm hours :h}
        {:dt 10 :mm 22 :h 12})

将扩展为以下内容:

(doto
  (new java.util.Date)
  (.setDate (:dt {:dt 10, :mm 22, :h 12}))
  (.setMinutes (:mm {:dt 10, :mm 22, :h 12}))
  (.setHours (:h {:dt 10, :mm 22, :h 12})))

这里要注意的一些事情:

1)您不需要引入新变量new-obj,因为doto会返回正在操作的对象。

2)您的映射应作为文字地图传递,否则您无法将密钥传递给.特殊形式。

3)您可以看到entity地图正在重复。你可以通过在宏中引入另一个绑定来解决这个问题:

(defmacro map-to [type mappings entity]
  (let [ent (gensym "entity")]
    `(let [~ent ~entity]
       (doto (new ~type)
         ~@(map (fn [[field entity-field]]
                  `(~(symbol (str ".set" (clojure.string/capitalize field)))
                    (~entity-field ~ent)))
                mappings)))))

现在它扩展如下:

(let [entity20047 {:dt 10, :mm 22, :h 12}]
  (doto
    (new java.util.Date)
    (.setDate (:dt entity20047))
    (.setMinutes (:mm entity20047))
    (.setHours (:h entity20047))))

在repl中:

user> (map-to java.util.Date {date :dt minutes :mm hours :h}
              {:dt 10 :mm 22 :h 12})

;;=> #inst "2016-09-10T09:22:48.867-00:00"

user> (let [ent {:dt 10 :mm 22 :h 12}]
        (map-to java.util.Date {date :dt minutes :mm hours :h} ent))

;;=> #inst "2016-09-10T09:22:48.899-00:00"

(由于我的时区(gmt + 3),该值提前三小时)

<强>更新

要获得所需的行为(使用get-in),您只需稍微修改此宏:

(defmacro map-to [type mappings entity]
  (let [ent (gensym "entity")]
    `(let [~ent ~entity]
       (doto (new ~type)
         ~@(map (fn [[field entity-field]]
                  `(~(symbol (str ".set" (clojure.string/capitalize field)))
                    (get-in ~ent ~entity-field)))
                mappings)))))

在repl中:

user> (map-to java.util.Date {date [:date :dt]
                              minutes [:time :mm]
                              hours [:time :h]}
              {:date {:dt 10} :time {:mm 22 :h 12}})

;;=> #inst "2016-09-10T09:22:41.935-00:00"

扩展为:

(let [entity20094 {:date {:dt 10}, :time {:mm 22, :h 12}}]
  (doto
    (new java.util.Date)
    (.setDate (get-in entity20094 [:date :dt]))
    (.setMinutes (get-in entity20094 [:time :mm]))
    (.setHours (get-in entity20094 [:time :h]))))

现在你可以再制作一个宏来自动创建映射函数:

首先,您需要一个函数来从类对象生成制造商名称:

(defn make-name [c]
  (->> c
       .getName
       (#(clojure.string/split % #"\."))
       (clojure.string/join "-")
       (str "create-")
       symbol))

user> (make-name java.util.Date)
;;=> create-java-util-Date

现在宏定义了从实体创建实例的函数:

(defmacro defmapper [type mappings]
  `(defn ~(make-name type) [entity#]
     (map-to ~type ~mappings entity#)))

这个将创建函数,即给定实体,将其转换为类实例。这只是一个普通的功能:

(defmapper java.util.Date {date [:date :dt]
                           minutes [:time :mm]
                           hours [:time :h]})

扩展为:

(defn create-java-util-Date [entity__20122__auto__]
  (map-to
    java.util.Date
    {date [:date :dt], minutes [:time :mm], hours [:time :h]}
    entity__20122__auto__))

在repl中:

user> (map create-java-util-Date
           [{:date {:dt 10} :time {:mm 22 :h 12}}
            {:date {:dt 11} :time {:mm 22 :h 12}}
            {:date {:dt 12} :time {:mm 22 :h 12}}])

;;(#inst "2016-09-10T09:22:18.974-00:00" 
;; #inst "2016-09-11T09:22:18.974-00:00" 
;; #inst "2016-09-12T09:22:18.974-00:00")