Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factor Clojure code setting many different fields in a Java object using a parameter map bound to a var or local

I would like to set a group of fields in a Java object from Clojure without using reflection at runtime.

This solution (copied from one of the solutions) is close to what I am after:

(defmacro set-all! [obj m]
    `(do ~@(map (fn [e] `(set! (. ~obj ~(key e)) ~(val e))) m) ~obj))

(def a (java.awt.Point.))
(set-all! a {x 300 y 100})

This works fine but I want the macro to be able to process a map of fields and values passed in as a var or as a local binding (i.e. not passed directly to the macro as above). The fields should be represented as keywords so the following should work:

(def a (java.awt.Point.))
(def m {:x 300 :y 100})
(set-all! a m)

I can't figure out how to do this using set! and the special dot form within a macro (or any solution that works as above without using reflection at runtime).

like image 601
optevo Avatar asked Jun 07 '26 09:06

optevo


1 Answers

For this, I would do compile-time reflection coupled with polymorphism.

(defprotocol FieldSettable (set-field! [this k v]))

(defmacro extend-field-setter [klass] 
  (let [obj (with-meta (gensym "obj_") {:tag klass})
        fields (map #(symbol (.getName ^java.lang.reflect.Field %)) 
                    (-> klass str (java.lang.Class/forName) .getFields))
        setter (fn [fld] 
                 `(fn [~obj v#] (set! (. ~obj ~fld) v#) ~obj))] 
    `(let [m# ~(into {} (map (juxt keyword setter) fields))] 
       (extend ~klass 
         FieldSettable 
         {:set-field! (fn [~obj k# v#] ((m# k#) ~obj v#))}))))

This allows you to extend field setters per class.

(extend-field-setter java.awt.Point)
(extend-field-setter java.awt.Rectangle)

Now set-field! works on either and can be used with reduce-kv on a map.

(def pt (java.awt.Point.))
(def rect (java.awt.Rectangle.))

(def m {:x 1, :y 2})

(reduce-kv set-field! pt m) 
;=> #<Point java.awt.Point[x=1,y=2]>

(reduce-kv set-field! rect m) 
;=> #<Rectangle java.awt.Rectangle[x=1,y=2,width=0,height=0]>

Where in the rect example the width and height fields were left unaltered since not specified in the map.

like image 121
A. Webb Avatar answered Jun 09 '26 00:06

A. Webb



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!