5 ms·
Can you explain why you think C# does those things better than Java? And what they correspond to in Java?
by hhjinks 4y ago
Can you explain why you think C# does those things better than Java? And what they correspond to in Java?
- titzer 4y agoSure. Reified generics, along the lines of C#[1], rather than Java, is the only sane design. Erasure of generics works fine in languages that have the "parametricity" property, where you cannot observe any characteristics of a polymorphic value. That doesn't hold as soon as you have casting. Java generics have a couple of unfixable problems. For one, you can have List<Foo> and List<Bar> both be passed to a place where Object is expected. But getting them back from that place and trying to recover what was known at creation time, would normally be done with a cast. But List<Foo> and List<Bar> are not runtime distinguishable from each other. Such a polymorphic cast in the code is just syntactic sugar; you'll get a compile-time warning, but no runtime check! E.g. You can get a naked List and cast it to a List<Bar> and then go ahead and try to use it like a List<Bar>. It will only fail when you get an actual non-Bar thing out of the list. It pointedly won't fail if you take your List<Bar> and pass it to other generic code that does more generic stuff with it. And it won't fail if the list is full of nulls, is empty, etc. In essence, it's completely dynamically typed at that point. The static generic types are lies. In reality, all generic Java code compiles down to non-generic code with runtime casts everywhere. That costs performance and means that you can screw up. Second, erasure also means that you cannot be polymorphic over primitive types in Java, as the VM doesn't support that in the bytecode. So you can't write really basic stuff like Vector<T>, array sorting code, and now, closures and lambdas, that manipulate primitives. All that has to be duplicated, once per primitive type, and for objects. Or you have to box stuff. So they added implicit boxing (autoboxing) so you don't have to type those characters. But they are there in the code. You're stuck with either duplicating for performance (hand-doing template specialization, if you will), or just creating an assload of garbage. [1] I say "along the lines of". I designed Virgil's generics system not knowing C#, but working from what I knew from ML. It ended up with a lot of the same choices, but I mapped ML's "unit" onto "void" and that works out nicely for zero-arg and/or zero-return functions. You can even have an Array<void> in Virgil! And none of it creates boxes or introduces unsafe casts.
- debug-desperado 4y ago"Static generic types are lies" yes, and it doesn't matter that much. You got the safety at compile time and if you didn't do an unsafe cast then you preserved it. The issue with primitive types and boxing is certainly noted. Hopefully Valhalla will address it and more. The problem with reified generics is that the same variance model must be adopted by all guest languages on the runtime. Hence you basically don't see any guest languages on the CLR, and efforts by languages such as Scala to port to the CLR failed due to problems interoping with C#. I think one of the JVM engineers "pron" has discussed this multiple times.
- titzer 4y ago> The problem with reified generics is that the same variance model must be adopted by all guest languages on the runtime. I think this is a fair observation, but it really boils down to "a dynamically typed compilation target is an easier target", which isn't all that surprising. > such as Scala to port to the CLR I am not as familiar with Scala's saga here, but I've heard multiple conflicting reports from Scala insiders, so I think this is a more complicated issue than just the generics model of the CLR.
- kaba0 4y agoYour first point is kind of making a big deal out of something that isn’t that big of a deal. The trivial solution is to just.. use the language’s static typing and don’t pass typed objects as Object. With a generic function nigh everything can be done type-safely (remember, if you don’t use class casts, your generic code will be completely type safe). Sure, there are some edge cases, but they are so edge-cases of an edge case that I really don’t think it is a great differentiator between the two platforms. Your second point is true though, and the fix is something that requires at least 6 PhD’s combined, but they are working on it.
- titzer 4y agoI mean, I agree. The static warnings will generally steer you away from the bad cases. From a source-language standpoint, if you don't consider the second-order effects of this design choice, it accomplished a fair fraction of the original goals. But more complex patterns I've seen people try to pass around java.lang.Class objects as a reification. That doesn't really work, because it's skin deep. With significant experience with reified generics, there's just a lot of patterns that you can't do in Java. I wrote a whole PLDI paper about it back in 2013. Reified generics mean you can do stuff like ad-hoc polymorphism without direct language support. Ironically enough, the little trick of hiding type arguments with subtyping but getting them back with casts is a powerful dynamic tool.