Java's strong commitment to bytecode-level backwards-compability is sometimes to the detriment of its feature-set (e.g. non-reifiable generic types due to type erasure), but this commitment is a large contributor to the platform's success.
In exchange, you pay with a minor nuisance: mostly the inability to overload methods with the same erased types. I think most would agree that having a truly polyglot VM that allows different languages to enjoy the data structures written in other languages is much more important.
Scala, for example, has something akin to reified generics, without any support from the JVM itself. It also allows for a decent level of interop with other JVM languages, though of course all you can really "export" to other languages is the least common denominator semantics required by the JVM.
Java could theoretically do something similar: Maintain type information that is visible to Java at compile- and run-time, while still compiling to code that is consumable from other languages without too much hassle. Really, personally, I only care about it being available at compile time. Haskell, for example, erases types much more aggressively than Java does, while also maintaining a high level of type safety.
I guess I don't know that it's backwards compatibility on the JVM that bothers me so much as Java's tendency to think of itself as being little more than a JVM macro assembler with C-like syntax.
I also want to point out that having a type system with a stronger handle on generics is about more than just ergonomics when overloading methods. As another example, Java's story for auto-mapping libraries (serialization/deserialization, lighweight ORM, stuff like that) is materially hindered by the inability to express things like "List<MyDataType>.class". And those sorts of things do have a material impact on developer productivity - when I'm working in Java, I find that I spend an inordinate amount of time writing, maintaining and code reviewing all the extra glue code that needs to be written to handle tasks that other languages make almost effortless.
It's not the lack of possible solutions, but more of a design philosophy.
> is materially hindered by the inability to express things like "List<MyDataType>.class
See Guava's TypeToken https://google.github.io/guava/releases/25.0-jre/api/docs/co...
> I find that I spend an inordinate amount of time writing, maintaining and code reviewing all the extra glue code that needs to be written to handle tasks that other languages make almost effortless.
Which is why the Java ecosystem offers a nice selection of languages to match almost everyone's taste. Many people like Java and feel productive using it, and those who don't can enjoy the great polyglot interop afforded by, among other things, generic type erasure.
: Although some PL theorists may, but this is an expression of their opinion, not something that comes out of the theory.
foo[A](x: A) = ??? // Implementation hidden for now
The principle of parametricity says that `foo` should act in the same way no matter what type of value it is called with. Type erasure ensures this, because it prevents `foo` from inspecting the (reified) type at runtime. If types are not erased then `foo` can behave differently when given, say, a `String` or an `Int`.
Why is parametricity important? It makes code easier to reason about. Parametricity means no special cases to remember. It makes everything more uniform and simpler. In fact there is only one valid implementation of `foo` above, and this is the identity function.
foo[A](x: A) = x
Perhaps worth pointing out that this doesn't apply to languages like Java and C# though (which I think is the most common comparison, since generic support is quite similar between the two, but Java has type erasure and C# doesn't) as both languages support `instanceof` and `getClass`, which would allow a function to change its behaviour based on the type of its arguments.
It seems that once the type of an object can be determined at runtime, the argument that it is beneficial to hide the type of a generic parameter is quite a bit weaker.
It also removes some useful features, eg, without type erasure, this is possible:
My above post was mostly about explaining the concept of parametricity, though, and not so much about justifying it.
The idea of inferring the set of possible implementations of a function from its signature is neat.
<T> void doStuff(Class<T> type)
There, now you are guaranteed by the compiler to have the type of `T` at runtime. With this simple a work-around, it seems silly that they just didn't properly reify them and call it a day. That being said, it's also such an easy work-around that it's simply annoying at this point when I need to include the extra parameter(s) to get the type.
Type erasure, as I use the term, is compatible with type specific optimisation. For example, the MLton compiler for Standard ML supports monomorphisation of polymorphic code, which in turn allows unboxing: http://mlton.org/Monomorphise The compiler is free to use whatever representation it likes, which includes a reified representation for use in, say, dynamic linking, so long as it doesn't break the language semantics (by exposing this to the programmer, for instance).
Rah rah I hope the authors peek on HN and see that their work has baying fans :)
You can do manual memory management, GC free code blocks and there is unsafe.
Additionally C# 7.x has safe stack allocation, spans, references to stack allocated data.
Looking forward to have some of those goodies in Java as well.
How much of those are the direct result of having value types in the language?
As far as the other features you mentioned, it would be nice to have them in the JVM as well.
They were already available in other languages like Modula-3 and Oberon(-2) before Java came to be, but Java team decided it was too complicated for their target audience.
Just like Sun was religiously against AOT, a feature only available in commercial third party JDKs, they also believed it was a matter of making the JIT good enough for escape analysis.
Thankfully the .NET team had other beliefs.
Up to 7.3 you could only stack allocate arrays (alloca() style) inside unsafe code blocks.
Starting with 7.3 you can safely stack allocate array of value types.
Android 7 brought the VM back.
Now it uses an interpreter hand written in Assembly that gathers PGO info for the JIT, which also performs PGO data gathering, then only the hot bits are AOT compiled to native code, when the device is idle.
Any change on the execution path or updates trigger the execution process from the beginning.
And from that point of view, ART on Android 5/6 was a runtime, turned into a VM on Android 7.
From your point of view, the CLR is also not a VM.
Or do you think now everyone is going to rewrite their Java libraries into Kotlin, using JVM 8 only features?