I would like to implement a naive non-lazy map
in Java with a Java loop.
My main concern is function invocation in java from Clojure.
Here is my code :
A class called NaiveClojure to implement functions using Java
package java_utils;
import java_utils.ApplyFn;
public class NaiveClojure {
public static Object[] map (ApplyFn applyfn, Object function, Object[] coll) {
int len = coll.length;
for (int i = 0 ; i < len ; i++) {
coll[i] = applyfn.apply(function, coll[i]);
}
return coll;
}
}
An abstract class called ApplyFn
package java_utils;
public abstract class ApplyFn {
public abstract Object apply (Object function, Object value);
}
So in Clojure I have
(defn java-map [f coll]
(let [java-fn (proxy [ApplyFn] []
(apply [f x]
(f x)))]
(seq (NaiveClojure/map java-fn f (to-array coll)))))
I tried
(doall (map inc (range 0 10000))) ;; 3.4 seconds for 10000 operations
(java-map inc (range 0 10000) ;; 5.4 seconds
My point is not to outperform map
(I implemented it as an example), I just want to do things like that with specific functions (not to reinvent the wheel of Clojure).
Is there a better way to pass functions like that ? (as an easy and faster way) And to improve my coding in general (I have a poor theoritical knowledge), do you know what is killing perf here ? I would say general typing like Object but I do not see anything else
Thanks
You have no cause for concern here, the way you are doing it is fine and efficient.
coll[i] = applyfn.apply(function, coll[i]);
This is a very normal way to go about this. When measuring it do please, as Valentin Waeselynck points out, remember to use a reliable microbenchmarking function and also keep in mind that benchmarking this kind of small code chunk in isolation is tricky.
When you generate a clojure function it produces a "normal" java class, with a method called apply. This will not be any slower to call because you are calling a function that was originally written in Clojure than it would be to call a method on a class that was written in the normal Java syntax. Once the Hotspot JIT finishes warming it up and inlining, it will likely be as fast as it would be without the method call (which is why benchmarking this kind of thing is harder than it intuitively should be).