读取字段名称并设置为其他对象的值的宏
Macro To Read Field Names and set to value from other object
我是 clojure 的新手,正在尝试重构我编写的一些代码,看起来像这样
(defn transform
[entity]
(let [new-obj (doto (SomeObj.)
(.setField1 (:field-1 entity)))
new-obj))
我有很多对象需要实现这个,但我想创建一个接受 entity
、prototype
例如:(SomeObj.)
和 map
的宏其中键是原型的字段名称,值是关键字向量,用于从 entity
中获取正确的字段。对于每个 key
我需要使用 (get-in map [value as a vector])
.
中的参数调用 .set + keyName
我希望我可以为每个新实体创建一个映射配置,并且只为特殊情况编写代码。这可以使用宏吗?
或者在 clojure 中是否有更惯用的方法来做到这一点。
是的,您可以使用这样的宏轻松完成此操作:
(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
returns 正在操作的对象
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))))
回复:
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)))))
回复:
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]))))
现在您可以再创建一个宏来自动创建映射函数:
首先你需要一个函数来从 class 对象中生成制造商名称:
(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#)))
这将创建函数,即给定一个实体,将其转换为 class 实例。这只是一个普通的功能:
(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__))
回复:
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")
我是 clojure 的新手,正在尝试重构我编写的一些代码,看起来像这样
(defn transform
[entity]
(let [new-obj (doto (SomeObj.)
(.setField1 (:field-1 entity)))
new-obj))
我有很多对象需要实现这个,但我想创建一个接受 entity
、prototype
例如:(SomeObj.)
和 map
的宏其中键是原型的字段名称,值是关键字向量,用于从 entity
中获取正确的字段。对于每个 key
我需要使用 (get-in map [value as a vector])
.
我希望我可以为每个新实体创建一个映射配置,并且只为特殊情况编写代码。这可以使用宏吗?
或者在 clojure 中是否有更惯用的方法来做到这一点。
是的,您可以使用这样的宏轻松完成此操作:
(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
returns 正在操作的对象
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))))
回复:
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)))))
回复:
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]))))
现在您可以再创建一个宏来自动创建映射函数:
首先你需要一个函数来从 class 对象中生成制造商名称:
(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#)))
这将创建函数,即给定一个实体,将其转换为 class 实例。这只是一个普通的功能:
(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__))
回复:
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")