用于基于映射调用Java setter的Clojure宏?

用于基于映射调用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]

我正在为BraintreeJava库编写Clojure包装器,以提供更简洁、更惯用的界面。我想提供一些函数来快速、简洁地实例化Java对象,如:

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