3

我想在 Clojure 的 Java 对象中设置一组字段,而不在运行时使用反射。

这个解决方案(从其中一个解决方案复制而来)接近我所追求的:

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

我无法弄清楚如何使用 set 来做到这一点!以及宏中的特殊点形式(或任何在运行时不使用反射的如上工作的解决方案)。

4

2 回答 2

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示例中的位置widthheight字段保持不变。

于 2014-04-16T20:33:18.437 回答
0

好的,这行得通。我不确定它是否比反射更快,如果有人有任何建议,我希望看到它做得更优雅。

(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
于 2014-04-16T15:36:48.773 回答