Yes you do have to use trampolines for mutual tail recursive function, because the JVM currently lacks support for it, however if you take a look at the Da Vinci sub-projects in OpenJDK, a prototype has been in the works for quite some time, led by none other than John Rose and given the attention that invokeDynamic is getting, I have no doubt that this will also happen on the JVM: http://openjdk.java.net/projects/mlvm/subprojects.html
Also, Scalaz great as it may be, it simply sucks in terms of the actual implementation, as it has loads of functionality that breaks when that shouldn't have been a possibility in the first place. Every time I happened to investigate issues related to Scalaz, I always ended up with a "WTF were they thinking" moment.
> I'd argue that today, you are better off with the .NET VM, as far as features are concerned.
That's bullshit. Speaking of tail calls optimizations, the bytecode available in .NET didn't even work at all on 64 bits .NET, prior to version 4.5 - that's right, before 4.5 the tailcall bytecode was considered more of a guideline. And Because C# doesn't use it, they never bothered to optimize it, so it has a pretty dramatic performance hit in comparison with normal function calls. And Mono probably doesn't do tailcalls properly even today, with F# being unusable on top of Mono for several years.
In terms of features - for example the CLR doesn't optimize virtual method calls. This is killing dynamic languages, or static languages that diverge from the C# OOP flavor in terms of polymorphism. IronRuby was never even close to what JRuby could do even when Microsoft was funding its development, simply by virtue of JRuby running on top of the JVM. And now ever since JDK 7 with invokeDynamic, the performance boost is so amazing at times compared to Ruby MRI, that people are running apps on top of JRuby for performance reasons ;-) The JVM for example can inline virtual method calls at runtime and can de-optimize those call sites in case invariants change or in case it notices that there are no performance benefits. This is something that really few other VMs can do.
invokeDynamic is amazing. It provides a way to override the default method resolution that the JVM does, while still benefiting from the same optimizations that the JVM does for normal method calls. It even has benefits for static languages such as Scala, for dealing with closures and Java 8 introduces a facility for initializing a value that can afterwards be treated as a constant, so there are big wins ahead for Scala in terms of performance for things like "lazy val", or structural/dynamic types and others.
Functional languages use a lot of persistent data-structures and persistent data-structures are by their nature wasteful in terms of short-term junk. You need a really good garbage collector to not suffer from this and the JVM really has the best garbage collectors available. You should see what it can do in a server-side environment with server instances being hit with thousands of reqs/sec per JVM process of real traffic. I have and it's freaking sweet.
Also, it's actually good that Java's generics aren't reified. Reification is only needed for languages like Java in which the type system sucks and stays in the way for more expressive/advanced type systems. Language implementers have to pull off a lot of tricks in order to work around CLR's reified generics. F# has 2 generics type systems in the same language, as Hindley-Milner can't be piggybacked on top of CLR's reified generics.
.NET still has some niceties, like stack-allocated value types, which is sweet if you want numbers that diverge from the standard primitives offered and to avoid boxing (and JRuby suffers a little because of this). But btw, here's another thing that the JVM can do - it can do escape analysis and for example it can decide to allocate certain short-lived objects straight on the stack if it sees that those objects don't escape their context.