4 ms·
First of all, Java's generics are already superior to C#'s. We've exchanged the minor inconvenience of not being able to have overloads that erase to the same t
by pron 4y ago
First of all, Java's generics are already superior to C#'s. We've exchanged the minor inconvenience of not being able to have overloads that erase to the same type with the ability to support multiple variance strategies rather than one that's baked into the runtime. That's why Java has Clojure and Kotlin and Scala and Ruby running on top of it with such great interop. And, as it turns out, when specialisation is really important -- for Valhalla's value types -- then it's invariant and doesn't require demolishing kingdoms. We've also added virtual threads years after C# had async/await, and now they're the ones stuck with that inferior solution, which we'll remain in the ecosystem even if and when they add user-mode threads.
But in this case, I don't see what additional kingdoms there would be to demolish. No Java code employs nullability types now just as it didn't in 2014. Optional does the limited job it needs to do rather well, and will become even better when we extend pattern matching, so there would be no need to demolish it even if and when we have some solution to nulls. As you can see here (https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/util/class-use/Optional.html https://docs.oracle.com/en/java/javase/19/docs/api/java.base...) it's used sparingly and in specific situations, definitely not as a general null replacement.
- tehlike 4y ago"Java generics is superior to C#" - That's a first for me. C# generics don't have to do boxing of types like java does, and overall have much better type safety between library boundaries, etc...
- pjmlp 4y agoThe guys from Scala.NET did mention .NET generics as one of the reasons they gave up on porting Scala to .NET. That is also a reason why .NET needed the whole effort to create DLR and the dynamic keyword, while Java only needed to add one additional bytecode invokedynamic.
- jimmaswell 4y agoC# also declined to make the mistake of type erasure.
- deleted 4y ago[deleted]
- BoorishBears 4y ago> First of all, Java's generics are already superior to C#'s. We've exchanged the minor inconvenience of not being able to have overloads that erase to the same type with the ability to support multiple variance strategies rather than one that's baked into the runtime. Working on the JDK I'm sure you're aware just how revisionist that take is: we got the version of generics that landed because of backwards compatibility concerns. One of the key selling points going from Pizza to GJ was that the result ran on then current JVM targets without modification. https://jcp.org/en/jsr/detail?id=14 https://jcp.org/en/jsr/detail?id=14 > C1) Upward compatibility with existing code. Pre-existing code must work on the new system. This implies not only upward compatibility of the class file format, but also interoperability of old applications with parameterized versions of pre-existing libraries, in particular those used in the platform library and in standard extensions. > C2) Upward source compatibility. It should be possible to compile essentially all existing Java language programs with the new system. Interop came at the expense of Java (not the JVM) having a worse generics story, since the incredibly onerous constraint of "works with parameterized versions of pre-existing libraries" forced other languages to then go and reinvent the exact same wheel in different ways. - > We've also added virtual threads years after C# had async/await, and now they're the ones stuck with that inferior solution, which we'll remain in the ecosystem even if and when they add user-mode threads. "Kingdoms to demolish" refers to changing generics, but there are "kingdoms" have been built that wouldn't be demolished: they still represent a lot of throwaway work from putting off attacking the problem. Case in point, the post we're commenting under. Nullability specifically is much more subject to the part you left out: "And if you fix it, it'll be years before it trickles down to a large portion of devs who don't get to work at the bleeding edge, or even near the edge." - At the end of the day it's always been a tradeoff between backwards compatibility, timeliness, and correctness, but when I switch between Java specifically and C# specifically (not the JVM and the CLR) I find C# did an amazing job providing value upfront rather than hand wringing, and is not nearly as worse off for it as this comment would imply. I mean you're looking down on async/await, but it came out a decade ago while Loom is just landing. I guess you're trying to paint that decade of development as "weighing down" whatever .NET comes out with because it'll still exist, but that seems like a stretch at best. Java had no async/await story and will get a very good alternative. C# had an async/await story and will get an improved alternative (it's already in the experimentation stage according to the .NET team). I'd just much rather have the latter?
- unscaled 4y ago> First of all, Java's generics are already superior to C#'s. That's... highly controversial to say the least. You didn't just lose the ability to overload List<String> and List<Integer>. You've also lost run-time type information and type-safety when doing reflection. And reflection-based metaprogramming cannot be ignored in Java (and to a lesser degree in Kotlin): So much is based on it! It also makes interfacing with primitives (especially arrays of primitives) quite messy. And you've also lost some possibilities for JIT optimizations, but I'm not sure if C# does them anyway and how much they really matter - so that part may be minor indeed. Allowing different variance strategies could be a boon for a language-agonstic runtime, but, as other comments mentioned, that's just rewriting history. The JVM wasn't built to be a language-agnostic runtime. The truth thoroughly documented. It started with a new Java-like language, Pizza, that had to run on the then-current JVM, and continued with a fork of Java called Generic Java. These attempts were independent at first, so they could not change the JVM. Type erasure was chosen because it was the best fit to the existing Java model and had the best performance with the JVM as-it-was[1]. In hindsight it turned out to make writing JVM languages with different variance strategies easier, but it's not a superior design, just a coincidence. Although Martin Odersky was behind both Pizza and Scala, he did not mention variance as concern back in the 1990s when he chose type erasure. Let's not try to rationalize a JVM-compatibility decision that was done by a team outside Sun as some great insight by "the brilliant engineers at Sun". In the end of the day, you can do type erasure just as well on .Net when you need it, the same way you'd do it in Java. Nothing prevents a CLR language from compiling List<T> as List<Object>. > We've also added virtual threads years after C# had async/await, and now they're the ones stuck with that inferior solution, which we'll remain in the ecosystem even if and when they add user-mode threads. Virtual threads are not inherently superior or inferior to the async/await mode. It's the old stackful coroutine vs. stackless coroutine or implicit-vs-explicit concurrency debate. I've already written about it multiple times in the past[2]: Stackful pros: * No "colored functions": I can write asynchronous I/O code the same way I write multi-threaded code or non-blocking code. It's definitely more ergonomic, but also means your code can hold more surprises for you. Not everybody thinks 'Colored Functions' are an absolute advantage, otherwise we wouldn't have Effect Typing. * Less GC allocations: stack is [re-]allocated in bulk every time time a lightweight starts (go statement) or whenever it needs to grow. Stackless coroutines typically allocate a smaller object on every call to an async function. Stackless pros: * Clear yield points: you always know the points where your coroutine could suspend and control would be moved to another coroutine: whenever you see an await statement. This is why function colors are needed - to be explicit about the control flow. * Less memory waste and less overall allocation size: the "compile-time stacks" generated by stackless coroutines are perfectly efficient. There is no unnecessary memory allocated. I personally have a strong preference for explicitly marking concurrency, just like I would love to distinguish pure function and functions that have outside effects: I think Effect Typing is generally a good thing. Java also went its own way with explicit and mandatory effect marking when it comes to checked exceptions. Unfortunately, it never really got composability right, and using checked exceptions is simply too verbose - but Rust also went for explicit error type signatures, and its solution is generally well-received. [1]: https://dl.acm.org/doi/abs/10.5555/647373.724066 https://dl.acm.org/doi/abs/10.5555/647373.724066 [2]: https://news.ycombinator.com/item?id=24361590 https://news.ycombinator.com/item?id=24361590