用于基于映射调用Java setter的Clojure宏?
我正在为BraintreeJava库编写Clojure包装器,以提供更简洁、更惯用的界面。我想提供一些函数来快速、简洁地实例化Java对象,如:用于基于映射调用Java setter的Clojure宏?,java,macros,clojure,interop,initialization,Java,Macros,Clojure,Interop,Initialization,我正在为BraintreeJava库编写Clojure包装器,以提供更简洁、更惯用的界面。我想提供一些函数来快速、简洁地实例化Java对象,如: (transaction-request :amount 10.00 :order-id "user42") 我知道我可以明确地做到这一点,如所示: 但对于许多类来说,这是重复的,当参数是可选的时,这会变得更加复杂。使用反射,可以更简洁地定义这些函数: (defn set-obj-from-map [obj m] (doseq [[k v] m]
(transaction-request :amount 10.00 :order-id "user42")
我知道我可以明确地做到这一点,如所示:
但对于许多类来说,这是重复的,当参数是可选的时,这会变得更加复杂。使用反射,可以更简洁地定义这些函数:
(defn set-obj-from-map [obj m]
(doseq [[k v] m]
(clojure.lang.Reflector/invokeInstanceMethod
obj (name k) (into-array Object [v])))
obj)
(defn transaction-request [& {:as m}]
(set-obj-from-map (TransactionRequest.) m))
(defn transaction-options-request [tr & {:as m}]
(set-obj-from-map (TransactionOptionsRequest. tr) m))
显然,如果可能的话,我希望避免反思。我试着定义一个宏版本的set obj from map
,但是我的宏fu不够强大。如前所述,它可能需要eval
有没有一种方法可以在不使用反射的情况下调用运行时指定的Java方法
提前谢谢
更新的解决方案:
按照Joost的建议,我能够使用类似的技术解决这个问题。宏在编译时使用反射来确定类具有哪些setter方法,然后抛出表单来检查映射中的参数,并使用其值调用该方法
以下是宏和使用示例:
; Find only setter methods that we care about
(defn find-methods [class-sym]
(let [cls (eval class-sym)
methods (.getMethods cls)
to-sym #(symbol (.getName %))
setter? #(and (= cls (.getReturnType %))
(= 1 (count (.getParameterTypes %))))]
(map to-sym (filter setter? methods))))
; Convert a Java camelCase method name into a Clojure :key-word
(defn meth-to-kw [method-sym]
(-> (str method-sym)
(str/replace #"([A-Z])"
#(str "-" (.toLowerCase (second %))))
(keyword)))
; Returns a function taking an instance of klass and a map of params
(defmacro builder [klass]
(let [obj (gensym "obj-")
m (gensym "map-")
methods (find-methods klass)]
`(fn [~obj ~m]
~@(map (fn [meth]
`(if-let [v# (get ~m ~(meth-to-kw meth))] (. ~obj ~meth v#)))
methods)
~obj)))
; Example usage
(defn transaction-request [& {:as params}]
(-> (TransactionRequest.)
((builder TransactionRequest) params)
; some further use of the object
))
您可以在编译时使用反射~只要您当时知道要处理的类~来计算字段名,并从中生成“静态”setter。不久前,我为getters编写了一些代码,您可能会觉得很有趣。请参阅(特别是中的def fields宏)。宏可以简单到:
(defmacro set-obj-map [a & r] `(doto (~a) ~@(partition 2 r)))
但这会使您的代码看起来像:
(set-obj-map TransactionRequest. .amount 10.00 .orderId "user42")
我想这不是你想要的:)没有思考?几乎可以肯定不是。嗯,可以使用宏将映射转换为方法调用而无需反射。我只是在意识到宏无法获取一个包含贴图的符号,而只能获取原始贴图本身时才使用了反射。我应该更清楚地指出,我希望避免在运行时反射,如下面描述的@joost diepenmaat。诚然,这种语法并不理想,也不能被函数的调用者使用。您仍然需要以某种方式将调用方参数映射到此语法。谢谢,这非常有用。我需要将我的方法颠倒过来,而不是在运行时使用任何映射参数进行反射,而是在编译时枚举setter。另外一个好处是,只检查有效的参数。
(set-obj-map TransactionRequest. .amount 10.00 .orderId "user42")