2012-02-28 14 views
10

Daha özlü ve deyimsel bir arayüz sağlamak için Braintree Java kütüphanesi için bir Clojure sarıcı yazıyorum. Ben this question gösterildiği gibi, açıkça bu yapabileceğini biliyorumJava ayarlayıcılarını bir haritaya göre çağırmak için Clojure makrosu?

(transaction-request :amount 10.00 :order-id "user42") 

: Java gibi, hızlı ve kesin nesneleri örneğini işlevleri sunmak istiyorum

(defn transaction-request [& {:keys [amount order-id]}] 
    (doto (TransactionRequest.) 
    (.amount amount) 
    (.orderId order-id))) 

Ama bunun için tekrarlanan Birçok sınıf ve parametreler isteğe bağlı olduğunda daha karmaşık hale gelir. Yansıma kullanarak, çok daha kısaca bu işlevleri tanımlamak mümkündür:

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

Açıkçası, eğer mümkünse, yansımayı önlemek istiyorum. set-obj-from-map'un bir makro sürümünü tanımlamayı denedim ancak makro fu'm yeterince güçlü değil. here açıklandığı gibi muhtemelen eval gerektirir.

Yansıma kullanmadan çalışma zamanında belirtilen bir Java yöntemini çağırmanın bir yolu var mı?

Şimdiden teşekkürler!

Güncellenen çözüm:

Joost tavsiyelerine uyan ben benzer bir teknik kullanarak sorunu çözmek başardı. Bir makro, sınıfın hangi ayarlama yöntemlerini saptadığını belirlemek için derleme zamanında yansıma kullanır ve daha sonra bir haritadaki paramı denetlemek ve yöntemi değeriyle çağırmak için formlara tükürür. Sonra ~ alan adlarını anlamaya sürece size göre uğraşıyoruz sınıfı bildiği gibi ~

; 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 
)) 
+0

tercih ediyorum ne değildir yansıma? Neredeyse kesinlikle değil. –

+1

Peki, bir makroyu kullanarak yansıma_uzunsuz bir metodu çevirmek mümkündür. Sadece makronun haritayı tutan bir sembolü alamadığını, ancak sadece ham haritanın kendisinin olduğunu farkettiğimde yansıma kullandım. Muhtemelen _runtime_'da yansıma yapmak istemediğimi belirtmek için daha açık olmalıydım, aşağıdaki gibi joost-diepenmaat gibi. – bkirkbri

cevap

8

derleme zaman yansıma kullanabilir ve:

Burada makro ve örnek bir kullanımı var bundan "statik" ayarlayıcılar üretin. Bir süre önce ilginç bulabileceğin alıcılar için oldukça fazla kod yazmıştım. Bkz. https://github.com/joodie/clj-java-fields (özellikle, https://github.com/joodie/clj-java-fields/blob/master/src/nl/zeekat/java/fields.clj numaralı alanlardaki makro alanları).

+0

Bu, düzgün bir yaklaşımdır. – amalloy

+0

Teşekkürler, bu gerçekten yardımcı oldu. Yaklaşımımı tersine çevirmem gerekti ve harita eşlemelerini almamaya, daha sonra çalışma zamanında yansıtmaya çalışmak yerine, derleyicileri derleme zamanında sıralamak zorunda kaldım. Ek bir bonus, sadece geçerli parametrelerin kontrol edilmesidir. – bkirkbri

1

makro kadar basit olabilir:

(defmacro set-obj-map [a & r] `(doto (~a) [email protected](partition 2 r))) 

Ama böyle bu kod görünmesi olacaktır: sanırım

(set-obj-map TransactionRequest. .amount 10.00 .orderId "user42") 

sen olmadan :)

+0

Doğru, bu sözdizimi ideal değildir ve aynı zamanda işlevinizin arayanları tarafından kullanılamaz. Hala arayan argümanları bir şekilde bu sözdizimiyle eşleştirmeniz gerekir. – bkirkbri

+0

vay harika. Bu yüzden clojure öğrenmeyi seviyorum. – Core