The following will work:
(map #(%1 "21") [identity])
However the following code fails
(map #(%1 "21") [.toString])
How do I create a seq/collection of Java methods?
Java methods are not first class citizens like functions in clojure. You can't pass java methods as parameters, return or store in variables and collections. But you can create function by memfn or just anonymous function which wraps original method call
(map #(%1 21) [(memfn toString)])
=> ("21")
(map #(%1 21) [#(.toString %)])
=> ("21")
Related
I have a set of Java classes that all implement the newBuilder interface (they are actually protobuf generated classes). I would like to pass the class as a parameter to a form that returns a function to create a new builder for that class.
(defn create-message-builder
[klass]
(. klass newBuilder))
I cannot get the form dynamically so that it calls the newBuilder static method on klass.
I found a macro on another SO post and made some modifications to support injecting it into my source:
(defmacro jcall [obj & args]
`(let [ref (if (and (symbol? ~obj)
(instance? Class (eval ~obj)))
(eval ~obj)
~obj) ]
(. ref# ~#args)))
When I attempt to call this macro:
repl> (jcall Contact newBuilder)
#object[com.skroot.Contact$Builder 0x5622de90 ""]
I get an error:
IllegalArgumentException No matching field found: newBuilder for class java.lang.Class
The same thing you would do in Java: use reflection to ask the Class object what methods it has, find the one of the right name, and call it with no arguments.
(defn class->builder [c]
(let [m (.getDeclaredMethod c "newBuilder" (into-array Class []))]
(.invoke m nil (into-array Object []))))
So I'm trying to make a Clojure macro that makes it easy to interop with Java classes utilizing the Builder pattern.
Here's what I've tried so far.
(defmacro test-macro
[]
(list
(symbol ".queryParam")
(-> (ClientBuilder/newClient)
(.target "https://www.test.com"))
"key1"
(object-array ["val1"])))
Which expands to the below
(.
#object[org.glassfish.jersey.client.JerseyWebTarget 0x107a5073 "org.glassfish.jersey.client.JerseyWebTarget#107a5073"]
queryParam
"key1"
#object["[Ljava.lang.Object;" 0x16751ba2 "[Ljava.lang.Object;#16751ba2"])
The desired result is:
(.queryParam
#object[org.glassfish.jersey.client.JerseyWebTarget 0x107a5073 "org.glassfish.jersey.client.JerseyWebTarget#107a5073"]
"key1"
#object["[Ljava.lang.Object;" 0x16751ba2 "[Ljava.lang.Object;#16751ba2"])
I guess the . is causing something to get evaluated and moved around? In which case the solution would to be to quote it. But how can I quote the results of an evaluated expression?
My goal is to convert maps into code that build the object by have the maps keys be the functions to be called and the values be the arguments passed into the Java functions.
I understand how to use the threading and do-to macros but am trying to make request building function data driven. I want to be able take in a map with the key as "queryParam" and the values as the arguments. By having this I can leverage the entirety on the java classes functions only having to write one function myself and there is enough of a 1 to 1 mapping I don't believe others will find it magical.
(def test-map {"target" ["https://www.test.com"]
"path" ["qa" "rest/service"]
"queryParam" [["key1" (object-array ["val1"])]
["key2" (object-array ["val21" "val22" "val23"])]] })
(-> (ClientBuilder/newClient)
(.target "https://www.test.com")
(.path "qa")
(.path "rest/service")
(.queryParam "key1" (object-array ["val1"]))
(.queryParam "key2" (object-array ["val21" "val22" "val23"])))
From your question it's not clear if you have to use map as your builder data structure. I would recommend using the threading macro for working directly with Java classes implementing the builder pattern:
(-> (ClientBuilder.)
(.forEndpoint "http://example.com")
(.withQueryParam "key1" "value1")
(.build))
For classes that don't implement builder pattern and their methods return void (e.g. setter methods) you can use doto macro:
(doto (Client.)
(.setEndpoint "http://example.com")
(.setQueryParam "key1" "value1"))
Implementing a macro using a map for encoding Java method calls is possible but awkward. You would have to keep each method arguments inside a sequence (in map values) to be a able to call methods with multiple parameters or have some convention for storing arguments for single parameter methods, handling varargs, using map to specify method calls doesn't guarantee the order they will be invoked etc. It will add much complexity and magic to your code.
This is how you could implement it:
(defmacro builder [b m]
(let [method-calls
(map (fn [[k v]] `(. (~(symbol k) ~#v))) m)]
`(-> ~b
~#method-calls)))
(macroexpand-1
'(builder (StringBuilder.) {"append" ["a"]}))
;; => (clojure.core/-> (StringBuilder.) (. (append "a")))
(str
(builder (StringBuilder.) {"append" ["a"] }))
;; => "a"
Given the following piece of code:
(map Integer/parseInt ["1" "2" "3" "4"])
Why do I get the following exception unless I wrap Integer/parseInt in an anonymous function and call it manually (#(Integer/parseInt %))?
clojure.lang.Compiler$CompilerException: java.lang.RuntimeException: Unable to find static field: parseInt in class java.lang.Integer
the documentation on java interop says the following:
The preferred idiomatic forms for accessing field or method members
are given above. The instance member form works for both fields and
methods. The instanceField form is preferred for fields and required
if both a field and a 0-argument method of the same name exist. They
all expand into calls to the dot operator (described below) at
macroexpansion time. The expansions are as follows:
...
(Classname/staticMethod
args*) ==> (. Classname staticMethod args*) Classname/staticField ==>
(. Classname staticField)
so you should remember, that Class/fieldName is just a sugar for getting a static field, neither static method call, nor reference to the static method (java method is not a clojure function really), so there is no static field parseInt in Integer class, while (Class/fieldName arg) calls a static method, they are two totally different operations, using the similar sugary syntax.
so when you do (map #(Integer/parseInt %) ["1" "2" "3" "4"]) it expands to
(map #(. Integer parseInt %) ["1" "2" "3" "4"])
(you can easily see it yourself with macroexpansion),
and (map Integer/parseInt ["1" "2" "3"]) expands to
(map (. Integer parseInt) ["1" "2" "3"])
It fails when it is trying to get a field (which you think is getting a reference to a method).
Integer/parseInt is a static method of Integer class, not a clojure function. Each clojure function is compiled to java class which implements clojure.lang.IFn interface. map expects clojure function (which implements IFn interface) as a first argument, however, Integer/parseInt is not.
You can check that in the clojure repl.
user=> (type map)
clojure.core$map
user=> (type Integer)
java.lang.Class
user=> (type Integer/parseInt)
CompilerException java.lang.RuntimeException: Unable to find static field: parseInt in class java.lang.Integer, compiling:(/private/var/folders/p_/psdvlp_12sdcxq07pp07p_ycs_v5qf/T/form-init4110003279275246905.clj:1:1)
user=> (defn f [] 1)
#'user/f
user=> (type f)
user$f
user=> (type #(1))
user$eval9947$fn__9948
Perhaps reading this stackoverflow question will help you understand what is going on.
You might prefer to do it without the Java interop:
(map read-string ["1" "2"])
or for a more safe variant:
(map clojure.edn/read-string ["1" "2"])
I personally prefer to minimize the use of Java as much as possible in Clojure code.
As for why you can't just pass the Java function, because map expects a function in Clojure, not a function in Java.
I have a Java class with a constructor taking a variable number of String arguments like this:
public Foo(String...args);
I'm trying to create a make-foo multimethod in Clojure to handle this:
(defmethod make-foo clojure.lang.ArraySeq [& args] (new Foo (into-array args)))
But when I call it with
(make-foo ["one" "two"])
I get: IllegalArgumentException No matching ctor found
I'd also like to be able to call it with
(make-foo '("one" "two"))
I see there are to-array variants for ints, floats, etc, but no String. So how do I handle this case?
make-foo as written would work if you called it like (make-foo "one" "two"), or you could remove the & from its definition and then pass it sequences.
In Clojure how can I read a public member variables of an instance of a Java class? I want something like:
(. instance publicMemberName)
I also tried:
instance/publicMemberName
but this only works with static methods
In Java, the class java.awt.Point has public fields x and y. See the javadocs here http://download.oracle.com/javase/6/docs/api/java/awt/Point.html.
In Clojure the dot macro works for fields and methods. This worked for me:
user=> (let [p (new java.awt.Point 2 4)] (.x p))
2
EDIT: The following also works (note the space between the dot and the p):
user=> (let [p (new java.awt.Point 2 4)] (. p x))
2
EDIT: I decided to make a complete example given that java.awt.Point has methods getX and getY in addition to public fields x and y. So here goes. First make a Java class like this:
public class C {
public int x = 100;
}
Compile it
$ javac C.java
Now move C.class into your clojure directory. Next start the REPL, import the class, and watch it work:
$ java -cp clojure.jar clojure.main
Clojure 1.2.0
user=> (import C)
C
user=> (let [q (new C)] (. q x))
100
Note the other way works too:
user=> (let [q (new C)] (.x q))
100
If your object follows Java bean convention of getFoo to access member field foo, and you only need read access (i.e. aren't going to be mutating your object), you can use bean. That'll give you an immutable Clojure map that mimics the object, and then you can use standard keyword accessors.
user> (bean (java.awt.Point. 1.0 2.0))
{:y 2.0, :x 1.0, :location #<Point java.awt.Point[x=1,y=2]>, :class java.awt.Point}
user> (:x *1)
1.0