Search code examples
clojuremacros

calling special form `set!` in a clojure macro


Given Java instance obj and a member name (string) "Foo", and a map conf, I am trying to generate Clojure code that will look like this:

(if (get conf "Foo")
    (set! (.Foo obj) (get conf "foo")
    obj)

And also, if I know that "SomeEnum" is a Java enum name, a code like this:

(if (get conf "SomeEnum")
    (set! (.someEnum obj)(Enum/valueOf SomeEnum (get conf "SomeEnum")))
    obj)

Here is what I came up with:

(defmacro set-java [obj conf obj-name]
  `(if (get ~conf ~obj-name)
     (set! (. ~obj ~(symbol obj-name)) (get ~conf ~obj-name))
     ~obj))

(defn lowercase-first [s]
  (apply str (Character/toLowerCase (first s)) (rest s)))

(defmacro set-java-enum [obj conf obj-name]
  `(if (get ~conf ~obj-name)
     (set! (. ~obj ~(symbol (lowercase-first obj-name)))
           (Enum/valueOf ~(symbol obj-name) (get ~conf ~obj-name)))
     ~obj))

Testing with macroexpand seems to give the right result, but after trying:

(defn ^Policy map->policy [conf]
  (-> (Policy.)
      (set-java-enum conf "CommitLevel")
      (set-java conf "durableDelete")
      (set-java conf "expiration")
      (set-java conf "generation")
      (set-java-enum conf "GenerationPolicy")
      (set-java-enum conf "RecordExistsAction")
      (set-java conf "respondAllOps")))

I got a weird endless loop of reflection warnings.

--- edit ---

after battelling with that for quite a while, I gave up threading (->) and ended with:

(defmacro set-java [obj conf obj-name]
  `(when (get ~conf ~obj-name)
     (set! (. ~obj ~(symbol obj-name)) (get ~conf ~obj-name))))

(defn lowercase-first [s]
  (apply str (Character/toLowerCase ^Character (first s)) (rest s)))

(defmacro set-java-enum [obj conf obj-name]
  `(when (get ~conf ~obj-name)
     (set! (. ~obj ~(symbol (lowercase-first obj-name)))
           (Enum/valueOf ~(symbol obj-name) (get ~conf ~obj-name)))))

(defn map->write-policy [conf]
  (let [wp (WritePolicy. (map->policy conf))]
    (set-java-enum wp conf "CommitLevel")
    (set-java wp conf "durableDelete")
    ;; more non threaded object manipulation
    wp))

So I am still not sure what was the reflection warning endless loop about, but this is hopefully handful as well, and can be improved further.


Solution

  • I think this is due to your interpolating ~obj multiple times in each macro. Is your "loop" actually endless, or is it just ~128 or ~256 steps long?

    In any case, the fix for that particular issue (whether or not it's the root cause of the problem you describe) is to wrap the form in (let [obj# ~obj] ...) and then refer to obj# below, so the argument is only interpolated once.

    You can (should!) do this with conf and obj-name too, but they're probably not actively causing problems, at least with the usage code you provided.