Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

The Java implementors seem slow to adopt language improvements, for example compare C# with full closures, expression trees, LINQ etc.. to Java, and even the push back of some stuff to Java 8 will still leave it behind the current implementation of C#.

However since I dont intend to use either Java or C# that particular language war isnt of interest too much, im more concerned with the JVM vs CLR.

Is this lagging-behind also applicable to the JVM?

Will Scala, Clojure etc.. will they be able to continue to innovate or score optimal performance in the face of slowly progressing underlying VM such as JVM? Is Clojure/Scala restrained at present by JVM limitations?

share|improve this question
    
it's worth noting also that Clojure targets the JVM, the CLR, and JavaScript (via google Closure). –  Arthur Ulfeldt Jul 29 '11 at 18:15
add comment

migrated from stackoverflow.com Jul 29 '11 at 17:54

This question came from our site for professional and enthusiast programmers.

4 Answers

There is a number of problems rooted deep in JVM which makes it literally impossible to implement high level languages efficiently. The mostly cited issue is the lack of explicit tail call annotations. Another notorious problem is a lack of explicitly stack allocated structures (value types in .NET). JVM method size limit is also an unfortunate factor for implementing optimising compilers - e.g., a huge state automaton can't be efficiently translated into a single method with gotos, you have to break it into a number of tiny methods and use a mini-interpreter to avoid having stack problems - and this solution is 10x times slower than an ad hoc one. It is also very limiting in presense of a proper macro metaprogramming (as in Clojure).

So, from a programming languages implementation point of view, CLR is a much more advanced platform than JVM.

As for Clojure being restrained by JVM limitations - see the recur form, it is there only for getting around that unfortunate JVM limitations. Non-JVM lisps work just fine without that.

share|improve this answer
8  
@Angel, why is it always "tail calls optimisation" people are talking about? It is not an optimisation. It is an important part of a language semantics. Imagine a code like this (Scheme): (define (f g x) (g x)). Here, (g x) is a tail call. You can't "optimise" it, as you've got no idea what kind of g can be passed. Compilers can't do anything with tail calls besides the trivial (and least interesting) cases when there is only a bunch of mutually recursive functions. And what you've said about "value classes" (sic) is also far from what is of an interest for a compilers designer. –  SK-logic Jul 29 '11 at 10:19
4  
"why is it always "tail calls optimisation" people are talking about? It is not an optimisation. It is an important part of a language semantics." Sorry, that is completely wrong. You can do tail calls in any language that has calls. And it is called optimization, because that CALL IS OPTIMIZED AWAY! And Scala e.g. is just doing that, and the JIT Compiler does it as well. Tail call optimization essencially is transforming a recursive call into a loop. That has nothing to do with VMs, regardly which you pick. Sorry, your 3 answers to me show that you are only repeating stuff you have read –  Angel O'Sphere Jul 29 '11 at 10:45
8  
@Angel, preventing a program from failing when it runs out of stack space is not an "optimisation". It is essential. Tail calls are not "optimised away" (besides that trivial cases I've mentioned) - technically they're still calls, but with the caller stack frame wiped out. Scala is not doing it, obviously, it is only capable of optimising the trivial mutual recursive calls cases (which, as I said before, are not iteresting at all). You simply failed to comprehend what is a tail call, if you keep talking about its most trivial form only (a tail recursion). –  SK-logic Jul 29 '11 at 10:51
3  
@Angel, no, JVM can not do that. You can't jump to a subroutine. You're still failing to understand what the tail call is. When you perform a method call (behold - it can be a virtual call as well) you've got no chance to get rid of a current stack frame. In .NET, if there is a .tail prefix present, a call is guaranteed to be a proper tail call, with your current stack frame eliminated. Still can't see a difference? –  SK-logic Jul 29 '11 at 11:38
3  
@chaotic3quilibrium, I'm answering a very specific question: what is it in JVM which restrains languages like Scala and Clojure. Of course there are scenarios where JVM kicks all the crap out of the other VMs - e.g., with its brilliant class GC (it had been only partially leveraged with an introduction of the dynamic methods in CLR). But still, when choosing a VM to implement an arbitrary programming language, I won't pick JVM for its limitations and will stick with Mono just to be sure VM won't dictate its limitations on a language I'm about to design. –  SK-logic Jul 29 '11 at 15:32
show 13 more comments

I am somewhat experienced with Clojure as a new JVM language and can confidently state that the language isn't particularly limited by the JVM. In particular it's worth noting:

  • Closures are fully possible and very efficient - this isn't a JVM limitation, more a limitation on Java syntax
  • Macro metaprogramming is great and works well on the JVM (far superior to anything in non-Lisp languages)
  • Dynamic typing is fully possible and works well
  • Lazy evaluation works excellently, and is actually often the preferred/idiomatic solution in Clojure rather than using recursion (that might need TCO).
  • Modern JITs actually use Escape Analysis to avoid heap allocation where it is unnecessary (e.g. an object used solely to contain a couple of return values from a function would actually get allocated on the stack)

There are couple of things that would be nice to have in the JVM for Clojure (I understand these will probably make it into future versions):

  • Proper tail call optimisation - Clojure allows explicit tail recursion with recur and you can use trampolines if you want non-stack-consuming co-recursion, but proper TCO would still be a nice optimisation.
  • Value types - again would be nice to have, there are a few cases where this would be very convenient and avoid some heap allocation.

On the other hand there are also advantages to the JVM:

  • Cross platform

  • In my experience, the GC and JIT compilation have been better on the JVM than on any other platform

  • A very comprehensive library / open source ecosystem

  • The JVM / JDK itself is available as open source (OpenJDK)

Overall the JVM has proven to be a very good platform for FP via Scala, Clojure and various other languages. I'll personally be sticking with the JVM because Clojure is amazing and cross-platform portability is important for my applications. I've certainly not seen any compelling reason to move to any other platform for server side applications.

share|improve this answer
3  
I would also say that I like that Clojure forces you to write "(recur ...)" to denote tail-call optimization. That way, the compiler catches it when you want to do a tail call but messed it up by not making it the last operation in the function. In Scheme, such programmer errors are left uncaught until you blow your stack depth limit at run-time. –  SuperElectric Oct 10 '11 at 19:00
1  
Yeah, I've found the Clojure (recur ...) construct quite helpful that way. It's proved to me that "full" TCO isn't actually much of a big deal - you can do everything you want to do pretty easily without it. –  mikera Oct 12 '11 at 4:11
6  
@SK-Logic - You seem to be trying to argue that TCO is what defines a functional language? That's just comical, it might be very useful sometimes but it's only a feature - this is like saying four-wheel drive is what defines a car :-) . Also I suggest checking your definition of tail call - you'll find that a tail call is defined as any call in tail position. Compiling this into a jump or a loop is an optimisation (TCO), which is the usual term for a change that reduces space and/or processing requirements but otherwise leaves the result of an algorithm unchanged. –  mikera Sep 19 '12 at 9:52
2  
Clojure supports the full lambda calculus including tail calls just fine without doing automatic TCO. Your own example (defn [g x] (g x)) works just fine in Clojure. You are mistaken if you think that TCO is required to support the lambda calculus (the lambda calculus doesn't actually say anything about implementation). All TCO means is that some constructs can execute with less stack space. This is nice to have but it's ultimately not a big deal - I have never found a situation where lack of TCO was a problem in the real world. Are you in research or academia by any chance? :-) –  mikera Sep 19 '12 at 11:06
7  
@SK-Logic - You make some interesting points and I was genuinely interested in your perspective. However you don't seem to be able to conduct a reasonable and polite discussion - it seems almost as if you have some sort of axe to grind. Also this is the wrong place for extended debate. Hence I see no point in continuing this discussion. Bye! –  mikera Sep 19 '12 at 11:37
show 11 more comments

I add a few points that are valid:

  • the .NET VM supports indeed "value types", which can be on the stack (when needed, e.g. if a local variable is declared or during parameter passing to methods)
  • OTOH in Java lots of types are also value types ... they are just hand implemented and they are always allocated on the heap (just to make this clear, java.lang.String is a good example)
  • the .NET VM supports signed and unsigned primitive types, working with "bytes" etc. in Java is a true nightmare if you do serious low level stuff
  • the .NET VM supports unsave code
  • however also the JVM can integrate C/C++ code via JNI

Both are stack based VMs, in contrast to Parrot or Dalvik, which simulate a register processor. That means the former do stuff like "push @A" "push 12" to place the value of varibale A and the literal 12 on the stack and then e.g. an op like "add" to replace the top two stack positions with the sum of them. Register based VMs do a "load A to R1" and "load 12 to R2" and an "add R1,R2 to R3".

The JVM recently added a new call instruction (in Java 7) to support dynamic calls in languages like smalltalk or groovy, I'm not sure if the current .NET VM already supports that as well. (Note, that only means you don't have to do the dynamic dispatch yourself, it does not mean it is not possible. For .NET it is at least planend to also support such calls in future, e.g. see Iron Python.)

There is absolutely nothing in the current JVM implementation that makes any high level language difficult to implement, with one notable exception: inheritance. If you want to use native java byte code (that means java class file format) based inheritance you are stuck with one parent class. However a few languages that support MI circumvent this problem by generating their own method dispatch code or generating artificial interfaces and using hidden delegation.

The .NET VM had for a long time the same limitation, I'm not sure if that got changed meanwhile.

The fact that there are 100ds of languages compiled to the JVM indicates that it is a very well adopted environment.

New languages usually target the JVM first as it is much wider used than the .Net VM which is stuck to windows.

share|improve this answer
1  
One more point to add: CLR is not really "stack based". There are instructions like "swap" in JVM, whereas there are no stack operations in CLR at all, nothing besides trivial "dup" and "pop". CLR is rather a form of an expression tree serialisation than a stack machine. It makes it much easier to implement an efficient JIT which translates this representation directly into SSA, bypassing a complicated abstract stack interpretation pass (as in HotSpot). –  SK-logic Jul 29 '11 at 11:18
    
Oki, I point you to this and after that I give up: en.wikipedia.org/wiki/Common_Intermediate_Language –  Angel O'Sphere Jul 29 '11 at 11:29
    
as for your comments about "100ds of languages" - 99% of them are the same, they're all either Java with bells and whistles or dynamic languages with no efficiency at all by design. When it comes to really different languages, JVM starts to suck badly. Fancy an ML implementation? No way! STG? Impossible, period. WAM? Inefficient, GC-bound. State machine? Interpreted only! –  SK-logic Jul 29 '11 at 11:33
    
What has a swap instruction to do with the fact that the CLR is stack based? And MLs for the JVM are plenty ... lol. Small list: en.wikipedia.org/wiki/List_of_JVM_languages more complete list: is-research.de/info/vmlanguages –  Angel O'Sphere Jul 29 '11 at 11:42
1  
please read carefully: I said that if you compare JVM and CLR, the latter is not a real stack machine. Terminology is irrelevant when it comes to practice. And in practice implications of this difference are important: JVM can be interpreted with an efficiency. CLR was never meant to be interpreted at all. OTOH, JVM is much harder to JIT-compile efficiently due to its too stack-machine nature, whereas a restrained CLR can be treated as if it is not a stack machine at all. Just try to comprehend a couple of JIT compilers for both VMs, than you'd get what I'm talking about. –  SK-logic Jul 29 '11 at 12:49
show 3 more comments

I have to add an example that I bumped into myself.

This isn't really a limitation, but rather a leaky abstraction, however the Scala language which is implemented on the JVM has been impacted by its underlying architecture.

The language differentiates between methods, that are defined using the def keyword, and functions, that are actually objects with an apply method.

You can read more about the effects of that in this question.

In short, this means that:

  • You can't have genericized lambda expressions
  • Sometimes you have to explicitly convert methods to function objects so they can be passed as arguments, with a really simple syntax though: f _
share|improve this answer
    
Why the down vote? –  akled Mar 13 at 18:01
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.