5
votes

Update

This question is largely a duplicate, but both the question and my answer below seem more expository. See Access Java fields dynamically in Clojure? for the previous question.


I am trying to look up the settings of fields in instances of Java objects from Clojure. The dot operator seems to fail in places where I would naively expect it to work.

For example, with these definitions in place...

(defn example-point []
  (let [instance (java.awt.Point. 1 2)]
     (list (. instance x)  (. instance y))))

(defn example-point-1 []
  (let [instance (java.awt.Point. 1 2)
        fields '("x" "y")]
    (map #(. instance (symbol %))
         fields)))

(defn example-point-2 []
  (let [instance (java.awt.Point. 1 2)
        fields (map symbol '("x" "y"))]
    (map (fn [field] (eval `(. ~instance ~field)))
         fields)))

I get these return values:

flood.core> (example-point)

(1 2)

That's great, but what if I want to supply the name of the field "programmatically"? That's what the other functions are supposed to do. To my naive way of thinking, they should return the same value as above. But they both give different errors.

flood.core> (example-point-1)

IllegalArgumentException No matching method found: symbol for class java.awt.Point clojure.lang.Reflector.invokeMatchingMethod (Reflector.java:53)

flood.core> (example-point-2)

CompilerException java.lang.RuntimeException: Can't embed object in code, maybe print-dup not defined: java.awt.Point[x=1,y=2], compiling:(/tmp/form-init1213427540543573506.clj:1:5659)

I'm stuck, can you help me figure this out?


Further update

I did try this, and it produces yet another error.

;; added in response to suggestion...
(defn example-point-3 []
  (let [instance (java.awt.Point. 1 2)
        fields (map symbol '("x" "y"))]
    (map (fn [field] (eval `(. instance ~field)))
         fields)))

flood.core> (example-point-3)

CompilerException java.lang.RuntimeException: No such var: flood.core/instance, compiling:(/tmp/form-init1213427540543573506.clj:1:5659)

2

2 Answers

3
votes

The following code sample reproduces the central ideas from Joost Diepenmaat's answer at Access Java fields dynamically in Clojure? in a self-contained way. For practical use, his java-fields package is on Clojars as well on Github, and supplies a fields function that can be applied to instances of Java classes.

(import java.lang.reflect.Field)
(import java.lang.reflect.Modifier)

(defn example-point* [] 
  (let [fields (filter #(let [m (.getModifiers %)]
                          (and (not (= 0 (bit-and Modifier/PUBLIC m)))
                               (= 0 (bit-and Modifier/STATIC m))))
                       (.getDeclaredFields java.awt.Point))
        instance (java.awt.Point. 1 2)]
    (map #(.get % instance) fields)))

This filtering is necessary for even for Points, which contain one static private field, as can be seen by evaluating (map #(identity %) (.getDeclaredFields java.awt.Point)).

However, if we start with known names of the public fields, then the following shorter answer works, and is somewhat closer to the spirt of the original question. Again this requires java.lang.reflect.Field.

(defn example-point** [] 
  (let [field-names '("x" "y")
        instance (java.awt.Point. 1 2)]
    (map #(.get (.getField java.awt.Point %) instance) field-names)))

Notice that the dot operator is not used here at all.

2
votes

The first error is a result of clojure evaluating the following form:

(. instance-expr (method-symbol args*))

If you pass the list as the second argument of the . special form it's being interpreted as a call to the method method-symbol with arguments args (ie (method-symbol args*) is not evaluated).

In the second case you unquoted the instance unnecessary. Since clojure evaluates instance symbol and substitutes it's value in the syntax-quoted form, the result in your case was something like:

(. <instance of point> x)

which then got passed to eval. Eval expects clojure code to evaluate, having instance there wasn't correct (. has to have a symbol as first argument).

One solution would be to not unquote instance in your example-point-2