Piszę opakowanie Clojure dla biblioteki Java Braintree, aby zapewnić bardziej zwięzły i idiomatyczny interfejs. Chciałbym, aby zapewnić funkcje instancji obiektów Java szybko i zwięźle, jak:Clojure makro do wywoływania ustawiaczy Java na podstawie mapy?
(transaction-request :amount 10.00 :order-id "user42")
wiem, że mogę to zrobić jawnie, jak pokazano na this question:
(defn transaction-request [& {:keys [amount order-id]}]
(doto (TransactionRequest.)
(.amount amount)
(.orderId order-id)))
Ale to jest powtarzalny dla wiele klas i staje się bardziej złożona, gdy parametry są opcjonalne. Przy użyciu odbicia, to jest możliwe do zdefiniowania tych funkcji znacznie bardziej zwięźle:
(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))
Oczywiście, chciałbym, aby uniknąć refleksji, jeśli w ogóle możliwe. Próbowałem zdefiniować makro w wersji set-obj-from-map
, ale moje makro-fu nie jest wystarczająco silne. Prawdopodobnie wymaga to eval
zgodnie z wyjaśnieniami here.
Czy istnieje sposób wywołania metody Java określonej w środowisku wykonawczym, bez użycia odbicia?
Z góry dziękuję!
aktualizowane rozwiązanie:
Po poradę Joost, udało mi się rozwiązać ten problem stosując podobną technikę. Makro używa refleksji podczas kompilacji, aby zidentyfikować, które metody ustawiające ma klasa, a następnie wypluwa formularze w celu sprawdzenia paramu na mapie i wywołania metody z jej wartością.
Oto makro i przykład użycia:
; 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
))
Bez odbicie? Prawie na pewno nie. –
Cóż, możliwe jest przetłumaczenie mapy na wywołania metod _ bez odbicia_ za pomocą makra. Użyłem tylko refleksji, gdy zdałem sobie sprawę, że makro nie może wziąć symbolu trzymającego mapę, a jedynie samą mapę surową. Prawdopodobnie powinienem być bardziej jasny, stwierdzając, że chciałabym uniknąć refleksji w _czasie_, jak @ joost-diepenmaat opisana poniżej. – bkirkbri