mattyb149 / clojure-jsr223

Automatically exported from code.google.com/p/clojure-jsr223
0 stars 0 forks source link

Supporting the two types of Invocable.getInterface(...) method (top-level methods Vs. classes/modules) #5

Open GoogleCodeExporter opened 8 years ago

GoogleCodeExporter commented 8 years ago
According to my understanding of JSR223 and Yoko Harada's post 
(http://yokolet.blogspot.com/2008/04/tips-for-jruby-engine-getinterface.html), 
there are potentially two ways of integrating Clojure using JSR223. For 
example, if I'd like to provide an HelloWorld implementation of 
java.lang.Runnable in Clojure, I can use both top-level methods or 
classe/module.

1/ Using top-level methods
The Clojure code would be something like:
(defn run []
    (.println printService "Hello World!"))

And the expected JSR223 usage would be:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("Clojure"); 
Invocable inv = (Invocable) engine;

engine.eval("(defn run [] (.println printService \"Hello World!\"))");
engine.put("printService", System.out);
inv.invokeFunction("run", null);
// OR
inv.getInterface(Runnable.class).run();

2/ Using classes/modules
The Clojure code would rather look like:
(def HelloWorld
    (proxy [Runnable] [] 
        (run [] (.println printService \"Hello World!\"))))

While the associated JSR223 usage would be:
engine.put("printService", System.out);
Object obj = engine.eval("(def RunnableImpl (proxy [Runnable] [] (run [] 
(.println printService \"Hello World!\"))))");
inv.invokeMethod(obj, "run", null);
// OR
inv.getInterface(obj, Runnable.class).run();

However, it looks like you current implementation and samples tends to mix both 
approach and, in particular, leads to the naming convention you established. 
What would be the cost of supporting both approaches in your project rather 
than a mix of these?

Original issue reported on code.google.com by romain.r...@gmail.com on 13 Jan 2011 at 3:38

GoogleCodeExporter commented 8 years ago
I think that method 2 can be discarded since it isn't idiomatic Clojure and 
working with "host" objects is supported for low-level stuff like 
Clojure-on-Clojure. Notice that you'd need to add (HelloWorld) at the end to 
get the object. I can see that's like the description in the spec: "using the 
methods of object X".

I realize that the spec talks about top-level functions and the option of the 
proxy class with a handler that delegates to invokable functions. But since 
there's no interpreter, an object implementing interface X must be created 
either by the intermediary call #getInterface() out of metadata from the 
Clojure runtime through Java reflection, or else come from the script itself 
taking advantage that Clojure lets you create exactly such a thing.

Under the current scheme, once the code is evaluated (ie, compiled), getting an 
interface and using it is Java all the way; there's no invocation roundtrip to 
Clojure per method call. This efficiency comes at what seems to me the very 
little cost of composing a (proxy) s-expression. The naming convention may seem 
arbitrary but it saves having to somehow map your vars to classes somewhere. 
I'll also add that (proxy) lets you implement any number of interfaces in a 
single script without name collisions.

Top-level functions is a convenience that is expensive in terms of implementing 
an interface on the fly, and later for each method call.

Original comment by abm221...@gmail.com on 13 Jan 2011 at 9:21