If Java had generics in the beginning, it would look a lot different and the type system would be more powerful and safe.
If Java had generics in the beginning, it would look a lot different and the type system would be more powerful and safe.
Take a look at C# which added it without many issues (because they were willing to break back compat of their bytecode). Go fits this model a whole lot better.
They just decided to release 1.0 without generics, instead of waiting for it to be 100% ready.
As Don clearly describes on his blog.
In fact, I'm a big fan of how Microsoft does versioning, supporting previous versions, but releasing major versions with backwards incompatible changes. You see this in DirectX, and IMO, that's why we're talking about DirectX and Vulkan, rather than AZDO OpenGL these days. It really gives you an opportunity to clean up the cruft.
Metal uses Objective-C/Swift, with shaders in C++14. The Objective-C runtime can be used as FFI.
DirectX, uses COM with HLSL (a C++ subset). Likewise any COM or .NET (via RCW) aware language can talk to it.
One of the reasons OpenCL lost to CUDA was being stuck with C, while CUDA offered C, C++, Fortran and any additional language that could target PTX.
Which was what eventually made them come up with SPIR and later SPIR-V.
NVN and LibCGNM are also based on C++ and C++ inspired shader languages.
All modern OO, with nice SDKs that handle font, texture, materials, maths, GPGPU debugging.
I wouldn't say "without many issues." It took a major surgery in the CLR codebase to add generics. The difference between CLR 1.1 without generics and CLR 2.0 with generics was huge. No area of code was untouched.
I was thinking of more fundamental changes to Java idiom and the standard library had generics been part of the design (e.g., how arrays work).
To the 2nd - The concrete types are separate, but they fit into a common interface hierarchy, so, at a source code level, they're plenty interoperable. For example, the (non-generic) IEnumerable interface has a Cast<T>() extension method that will convert it to a (generic) IEnumerable<T>. IEnumerable<T>, for its part, simply inherits IEnumerable.
In general, I actually like using those conversion methods for downcasting, because it gives a clearer indication of when I'm in a danger zone - for example, converting a non-generic list to a generic list might fail if the non-generic list contains a mix of different types of object. Java's situation feels less predictable to me, as this article illustrates fairly well. A few keystrokes for the sake of safer code is a dandy tradeoff in my book.
That's not true. (Mass shared hoster kinda guy here) when we pushed a bunch of customers code compiled for .NET 1.0/1.1 over to hosting environments with only CLR 2 available, their code ran just fine. These were .NET 1.0/1.1 assemblies.
There are some edge cases where stuff could break, say when doing reflection or emitting your own IL, but that for most LOB web hosted apps was pretty rare.
I did in fact marvel how backwards compatible CLR 2 was when it first shipped.
When there is a C# class that both has and doesn't have type information, they are actually completely different classes that just happen to share a base name.
This really made it painful when they first added generics. If you used generic containers but wanted to call into a library that predated it you hand to transform your data in/out of all the calls to the library. In Java land you didn't have to do anything other then a blind cast on the out side which doesn't even have a runtime penalty.
Long term the C# way was probably better but then they didn't have nearly as robust of a library ecosystem when they added generics.
In Java's case, old bytecode using Map would correctly work with change to Map <K, V>. C# introduced a new set of collections I believe.
Except for the fact that all CLR languages must adopt the same variance model, enforced by the runtime (or choose not to interoperate well).
I also read, more generally, that adding reified generics to the JVM might make it hard or impossible to implement some dynamic languages on top of the JVM.
So yes, erasure has its drawbacks, but it's not all bad.
Maybe it's why the JVM has a larger ecosystem of new languages?
[0] Interfaces can contain other interfaces, but I think that is still different because you can't do
var a []subinterface = []superinterface{...}[0]: http://mattwarren.org/2018/03/02/How-generics-were-added-to-...
For what it's worth, Brian Goetz doesn't see type-erasure as a such a failure and defends the design choice[0].
Erasure is the safest way to implement generics for a long list of reasons. Reification comes with a lot of downsides, which is why most languages that support parametric polymorphism use erasure.
Aside from the video I posted from Brian Goetz most of what I've read is just comments about how much people like reification in C# and how annoying those edge cases in Java are (i.e. stories from practitioners not language designers.)
I'd love to read more.
1. Erasure keeps you honest and prevents you from second guessing the compiler by limiting the introspection you can perform on your code.
2. Reification puts up a very high barrier to interop. Erasure systems are much more welcoming to implementing multiple languages and multiple type systems on top of them. For example, the scala.net project was abandoned because it was impossible to represent Scala's type system on .net's reified platform.
3. Reification imposes overhead because of the multitude of extra runtime checks that need to be performed.
4. Most the benefits you get from reification can be emulated on erased systems (some are admittedly more hackish on an erased runtime, but they should be rare).
> Erasure keeps you honest and prevents you from second guessing the compiler by limiting the introspection you can perform on your code
But the title of the article under discussion is called "The Java type system is broken", after all :)
It would have been possible to implement reified generics while preserving backward compatibility, Neal Gafter had actually a proposal to do just that.
In the end, erasure won simply because it's the superior solution.
Erasure gives you a little less flexibility to express certain constructs than reification allows, but from a type safety standpoint, the two approaches are equivalent.
If you want widen the meaning of "type safe" a bit, I would argue that reification is "less" type safe in the sense that it allows you to perform additional runtime type checks / type casts, which basically means you are second guessing the compiler and invalidating all the type soundness that it has provided you by accepting to compile your code.
And conceivably a good deal faster, too. My understanding is that the JVM is required to constantly perform runtime type checks.
I remember Java before generics: tons of casting from Object. The compiler is doing the same thing.
No. That's the point.
For dynamic or tag-free languages, type indicators could be used to parse-check scalar (base) values to see if they are interpretable as the intended type:
function foo(int a, date b) {...}
This would be equivalent to: function foo(a, b) {
if (! parsableAsInt(a)) throwTypeError(...);
if (! parsableAsDate(b)) throwTypeError(...);
}
And don't overload operators, such as how some languages use "+" to mean both arithmetic addition and string concatenation. Use a different symbol for concatenation like PHP does.You have to reason about types in any language because passing the wrong type to a function can result in errors. You have to do all of that mental work by yourself in a dynamic language because there isn't a system to do it for you.
The purpose of types is to introduce errors in the first place - at compile time, if you messed up.
Wrong code causes errors regardless. Annotating with types just moves this event from runtime to compile time.
Having used reflection in C# with both nullable types and non-, I'm curious what you mean by "scavenger hunt".