Paul, I'm going to combine two posts of yours in to one, if I may, and 
answer some of your points.

(Aaron, can we add some of this to the wiki, too? I'm including at least one 
correction)

*-- For version 5 of the JVM, I agree, and if your goal is to maintain 
compatibility with version 5 of the JVM, sure. However, there is lots of 
invokedynamic goodness that Clojure could benefit from.*

I can think of a few ways in which it would be possible to distribute a 
clojure.jar that supports JVM 7 features while still falling back to JVM 5 
compatibility. So, I don't think this was ever a barrier. But you obviously 
unconvinced by my report! Thank you for challenging me on this, As I said, 
I'm by no means 100% sure I exhausted the issue. I would encourage you to 
try out your ideas.

*-- If you don't provide any type hints, then the constructor for 
InstanceMethodExpr sets method = null and warns you that you're doing 
reflection (if you have the warn-on-reflection var set) 
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L1364
 
then in the emit function, when method == null it compiles the invocation to 
a call to call to Reflector.invokeInstanceMethod 
(https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L1456
 
and 
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Reflector.java#L25)
 
which does reflection at runtime everytime you invoke the 
method...*everytime*. With invokedynamic you can do the lookup in a 
bootstrap method for the invokedynamic call site, you get a MethodHandle to 
the method you found, and you cache it in a CallSite. Then the next time you 
come through you just use the MethodHandle in the CallSite. That's a huge 
performance boost. With invokedynamic you can do the same thing for static 
field access. Clojure cannot optimize static field access like this.
*
You are right! This is perhaps the only place where invokedynamic can be 
useful for Clojure. *However*, to me this is not a big deal, because the 
workaround of allowing for type hinting solves the problem just as well, and 
perhaps even more efficiently from the compiler's point of view. All it does 
is require some extra annotations by the programmer. The fact that Clojure 
emits a warning seems good enough for me, personally.

*-- I may be missing something, but I don't see how JRuby needs different 
classes.*

I recommend this old writeup by Charles Nutter for a good explanation of why 
JRuby *could* have used classes to solve the optimization problem (JSR-292 
changed a lot since 2008, but the principles remain pretty much the same):

http://blog.headius.com/2008/09/first-taste-of-invokedynamic.html

I would add one emphasis to his explanation --

He point outs, very rightly, that the JVM has no real static typing at the 
low level, *except* when it comes to linking methods. But there is in fact 
another serious limitation of the JVM that comes into play here, which might 
seem very obvious to him but not so much to Clojure folk: the smallest 
container unit of code for the JVM is a class. There is no such thing as a 
free-floating procedure that you can just invoke whenever. (Changing this 
would be a revolution in the JVM, because so much depends on the ClassLoader 
mechanism, from security to modularity in OSGi.) This means that any time 
you want to reuse code you need to create a class. (I forget which verson if 
the JVM it was -- 5? -- that it become possible for the garbage collector to 
unload classes. This was a great feature for dynamic languages like Rhino, 
which generated a lot of classes during runtime to solve different problems 
in dynamics.)

The class problem even worse for dynamic languages, because classes cannot 
be changed runtime, so if you hoped that you could just create one huge 
class and keep adding static methods to as your "free-floating" code, this 
again. This is a problem not only for JRuby, but also for implementing 
delegates in the Java, a long-request feature. Java thus needs to do 
ridiculous things like this, which in C# (for example) would be done so much 
more elegantly:

new Thread(new Runnable() { public void run() { ThisIsMyFakeDelegate(); 
}).start();

What we did above was create a new (anonymous) class as well as an instance! 
Clojure does essentially the same thing per fn, extending AFn instead of 
implementing Runnable.

* Correction: In my original report, I said that Clojure creates only 
instances of AFn, not classes. I was misstating the situation: it's exactly 
one class and one instance per fn. Clojure extends AFn and simply overrides 
the correct method according to arity. (Perhaps I should emphasize that all 
method arguments are also of type Object, since no type checking is done 
explicitly or even known at compile time. So, no further method signatures 
are necessary other than one per number of Object arguments.) There is no 
problem with proliferation of classes here, because Clojure does not 
normally create many new functions at runtime. (I would only see this 
happening if you somehow embedded the REPL into Clojure and evaled code that 
created functions. But, unless you bind them somewhere, the classes would be 
garbage collected. If you are binding them, you should reconsider your 
solution, as something like that would probably cause any Lisp to choke...)

*-- Clojure has similar needs. For instance, Clojure has protocol dispatch 
and multimethod dispatch as well as Var based dispatch. The multimethod 
dispatch does a look up in the derivation hierarchy. All of this stuff can 
change at runtime, just like in Ruby you can add methods to a class, change 
methods on a class, etc. I don't see how Clojure's dispatch needs are much 
different than what other dynamic languages are doing.
*
So, perhaps I'm not understanding how you are using the term "change at 
runtime" to describe both JRuby regular dispatch and Clojure's multimethod. 
Actually, this is a good place to again explain why Clojure is so different, 
and why I didn't even consider (perhaps prematurely?) multimethod as a place 
where invokedynamic could help:

Ruby lets you change classes and modules at runtime, because from Ruby's 
perspective a class (or module) is simply a dictionary of methods. So, in 
such cases it's perfect that the CallSite can be invalidated, and force the 
JVM to call your bootstrapping method again every time you change the dict. 
So, "dynamic" in this sense means that the class/module dict is mutable, and 
invokedynamic allows for an elegant optimization. In Clojure, the 
"class/module" construct does not exist, and functions are bound to 
Vars/Refs/Atoms, if they are bound at all, which must always be dereferenced 
anyway.

(On further thought, I'm wondering now if we can do something with 
invokedynamic here after all. We may be handle such bound functions through 
invokedynamic linkage instead via dereferencing, effectively moving the 
binding to a deeper level. This may involve very different implementations 
per Var/Ref/Atom respectively. I'm going to look into this some more -- we 
have to be sure not to break Clojure's semantics and limit its flexibility 
here.)

Clojure's multimethod is "dynamic" in an entirely different way: the correct 
function is selected at runtime *according to the arguments* of the 
function. JVM-level linkage to the selected method would make no sense, 
because you'd be calling your bootstrapping method every single call!

You can argue that Clojure multimethods are inherently inefficient, and 
perhaps they are.

One solution I could see is we allow to expand Clojure's deftypes into 
something more like classes/modules in other languages, in that deftypes 
could encapsulate functions as well as fields. But, this would turn Clojure 
into essentially object-oriented language, and I sincerely doubt Rich would 
allow for that to happen. :)

Hope this helps clarify things! I don't think I've answered every single one 
of your points, but this discussion is already probably too long for more 
readers. Again, I encourage you to try to implement your ideas. The more 
eyes are on this problem, the better.

-Tal

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to