Search code examples
clojureclojure-java-interop

Calling a list of methods on a Java object using Clojure


I have a list of values I want to convert to an immutable Java POJO. The Java POJO gets generated using the builder pattern.

The list I have is:

[49.0 11.1 150.0]

In Java my POJO would get constructed like this:

Position.builder().latitude(49.0).longitude(11.1).altitude(150.0).build()

I thought to create it in Clojure I would first zipmap the values with the methods that need to be used and then I could reduce-kv everything to the Java value I need:

(defn poslist->map [list]
      (zipmap ['.latitude '.longitude '.altitude]
              list))

(.build (reduce-kv #(%2 %1 %3) 
                   (pkg.Position/builder)
                   (poslist->map list)))

My problem now is that I don't get what I expect. The function poslist->map returns the expected value:

{.latitude 49.0, .longitude 11.1, .altitude 150.0}

But the reduce-kv just returns the last value I have:

150.0

Why don't I get back the builder on which I then can apply the .build method? I had expected that the reduce-kv to have the same result as the following code that returns what I expect:

(.build (.altitude (.longitude (.latitude (pkg.Position/builder) 150.0) 11.1) 49.0))

And as the reduce-kv function returns a double the following call of .build failes with

No matching field found: build for class java.lang.Double

BTW: The reason I am mapping the methods to the list values is, that the actual list of fields I have to set is longer as in the example. I stripped down the example to be more readable while retaining my basic problem.


Solution

  • While Clojure symbols act as functions, they are not connected with functions with the same name in any way:

    > ('.add (java.util.ArrayList.) 1)
    1
    > ('first [1 2 3])
    nil
    > ('conj [1 2] 3)
    3
    

    So the issue, basically, is that java methods are not called at all. To fix it wrap your iterop methods with memfn:

    ['.latitude '.longitude '.altitude]
    
    ->
    
    [(memfn latitude val) (memfn longitude val) (memfn altitude val)]
    

    or, as @noisesmith suggested, with clojure functions:

    ->
    
    [#(.latitude %1 %2) #(.longitude %1 %2) #(.altitude %1 %2)]