Estoy escribiendo un contenedor Clojure para la biblioteca Braintree Java para proporcionar una interfaz más concisa e idiomática. Me gustaría ofrecer funciones para crear instancias de los objetos Java de forma rápida y concisa, como:¿Macro de Clojure para llamar a los instaladores de Java basados en un mapa?
(transaction-request :amount 10.00 :order-id "user42")
Sé que puedo hacer esto de manera explícita, como se muestra en this question:
(defn transaction-request [& {:keys [amount order-id]}]
(doto (TransactionRequest.)
(.amount amount)
(.orderId order-id)))
Pero esto es repetitiva para muchas clases y se vuelve más complejo cuando los parámetros son opcionales. El uso de la reflexión, es posible definir estas funciones mucho más concisa:
(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))
Obviamente, me gustaría evitar la reflexión, si es posible. Traté de definir una versión de macro de set-obj-from-map
pero mi macro-fu no es lo suficientemente fuerte. Probablemente requiera eval
como se explica en here.
¿Hay alguna manera de llamar a un método Java especificado en tiempo de ejecución, sin usar el reflejo?
¡Gracias de antemano!
solución actualizada:
Siguiendo el consejo de Joost, yo era capaz de resolver el problema utilizando una técnica similar. Una macro utiliza la reflexión en tiempo de compilación para identificar qué métodos de establecimiento tiene la clase y luego escupe formularios para buscar el parámetro en un mapa y llamar el método con su valor.
Aquí está la macro y un ejemplo el uso:
; 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]
[email protected](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
))
Sin ¿reflexión? Casi seguro que no. –
Bueno, es posible traducir un mapa en llamadas a métodos _sin reflejo_ usando una macro. Solo utilicé la reflexión al darme cuenta de que la macro no podía tomar un símbolo que sostenía el mapa, sino solo el mapa en bruto. Probablemente debería haber sido más claro al decir que me gustaría evitar la reflexión en _runtime_, como @ joost-diepenmaat se describe a continuación. – bkirkbri