Код фактора Clojure устанавливает множество различных полей в Java-объекте, используя карту параметров, привязанную к var или local
Я хотел бы установить группу полей в Java-объекте из Clojure без использования отражения во время выполнения.
Это решение (скопированное из одного из решений) близко к тому, что я ищу:
(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})
Это прекрасно работает, но я хочу, чтобы макрос мог обрабатывать карту полей и значений, передаваемых как var или как локальную привязку (то есть не передаваться непосредственно в макрос, как указано выше). Поля должны быть представлены как ключевые слова, поэтому должно работать следующее:
(def a (java.awt.Point.))
(def m {:x 300 :y 100})
(set-all! a m)
Я не могу понять, как это сделать с помощью набора! и специальная точечная форма внутри макроса (или любое решение, которое работает, как указано выше, без использования отражения во время выполнения).
2 ответа
Для этого я бы сделал отражение во время компиляции в сочетании с полиморфизмом.
(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#))}))))
Это позволяет расширить наборы полей для каждого класса.
(extend-field-setter java.awt.Point)
(extend-field-setter java.awt.Rectangle)
Сейчас set-field!
работает на любом из них и может быть использован с reduce-kv
на карте.
(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]>
Где в rect
пример width
а также height
поля были оставлены без изменений, так как не указано на карте.
Хорошо, это работает. Я не уверен, что это быстрее, чем размышление, и я хотел бы, чтобы это было сделано более элегантно, если у кого-то есть какие-либо предложения.
(defmacro set-fn-2 [f]
`(fn [o# v#] (set! (. o# ~f) v#)))
(defmacro set-fn-1 [f]
`(list 'set-fn-2 (symbol ~f)))
(defn set-fn-0 [f]
(eval (set-fn-1 (symbol (name f)))))
(def set-fn (memoize set-fn-0))
(def a (java.awt.Point.))
(def val-map {:x 1 :y 2})
(defn set-all! [o m]
(doseq [k (keys m)] ((set-fn k) o (m k))))
(set-all! a val-map)
a