4 ms·
I haven't read the article fully, but can anyone please explain why value types seem so much more complicated in the JVM than DotNet, where they have had it for
by akkad33 3y ago
I haven't read the article fully, but can anyone please explain why value types seem so much more complicated in the JVM than DotNet, where they have had it for a long time?
- whizzter 3y agoValue types was in the CLR(_Common_ Language Runtime) from day one as the runtime supported (variations of) C/C++,etc. Iirc proper generics didn't appear in version 1 of C# but since the foundation was there generics could become an addon in terms of new collection classes that could be properly parametrized. Java generics took a shortcut by reusing the old classes and just layering them on in the language but erasing all useful information to the runtime, and this is what's biting them in the back now when practice has shown that flat memory layouts is highly beneficial in terms of performance as CPU speeds has outstripped memory latencies. A List<int> in C# will be 2 objects, the List object and the underlying int[] array of sequential numbers, a Java List<int> will be 2+N objects, the List object, the object[] array and N boxed int objects, and the if N int objects are scattered in memory then traversing the integers will be far more expensive due to memory latencies.
- pjmlp 3y agoGenerics were also already on an experimental branch by the time .NET 1.0 was released. Microsoft decided not to delay the release waiting for them to get ready. Don Syme of F# fame has a couple of blog posts with the history of generics in .NET, as he was part of the original design team.
- kaba0 3y agoRegarding List<int> and specializing it to an int[] has more to do with generics than value types. The most important semantic info about a value type is not having identity, that is modifying it is not observable from another thread that were holding the same value previously. This alone lets one do things like freely copy/share/modify them, that directly allows for flattening.
- whizzter 3y agoIn theory you're right, in practice however the details of the JVM instruction set and the existing generics system throws wrenches into it. The JVM has instructions like iload, aload, dload,etc (integer, reference, double) to load values from stack slots, the simple instructions were probably chosen to support direct HW execution that was attempted for various embedded Java scenarios. They were unwilling to extend the instruction set back in the day when generics were introduced due to these type specific instructions and they just decided that everything generic could be an object. The CLR(.NET/C#) equivalent of iload,aload,etc is ldloc , a single instruction whose type depends on the stack slot, generic classes "just" need to specialize the type of slots,etc to have a specialized class, be it a primitive, reference or value. What they're doing now according to the article is to make aload and the a- family of instructions behave more like the CLR counterparts by adding slot flags, analysis and optimizations to detect "value-cases", so List<int> would be List<Integer(notnull)> and then optimized back down to List<int>. It's basically a hack upon the hack because they want to preserve backwards compat to the previous hack. If they can get the engineering sorted then sure, I'm mostly glad I won't have to maintain any code related to all this machinery. And I'm quite curious about how the JVM-lang ecosystem feels about this, maybe they're open to it if it solves backwards compat under the hood for them also, but I wouldn't be surprised if we're going to see pre-JVM 25 versions in addition to post JVM 25 versions of JVM langs in some cases.
- eonwe 3y agoI’d say that it’s backwards compatibility. My understanding is that moving from 1.1 to 2.0 (that introduced reified generics) required work from developers of libraries. It was also done in relative infancy of the ecosystem. But I trust some c# oldtimer to tell me how it actually was as I only remember that some tools I used insisted to have older .net runtime installed.
- Kwpolska 3y agoValue types (structs) were in C# 1.0, and they’re used e.g. in native code interop. Java generics use erasure, and they are backwards-compatible with non-generic-using code. You can still say `var l = new ArrayList();` in the latest Java versions; you’ll get a compiler warning, but the code will compile and run as well as code using `ArrayList<object>` would. C# uses reified generics (which are faster, saner, and more expressive), and standard collections exist in two namespaces (System.Collections vs System.Collections.Generic). If you needed to work with legacy code that uses the non-generic types, System.Collections.Generic.List<T> implements System.Collections.IList (but the code would need to be smart enough to demand the IList interface instead of the concrete System.Collections.ArrayList implementation).
- kaba0 3y ago> reified generics (which are faster, saner, and more expressive I wouldn’t go as far to claim that, e.g. it is often claimed to be the reason behind why the JVM has a blooming language ecosystem, while the CLR, not so much. Generics have language-level semantics and they may decide to do it differently, in which case erasure gives better results.
- tadfisher 3y agoA short way to describe this: you have to throw away all high-level type information in order to execute code on real machines, so the choice becomes when to throw it away. CLR languages throw it away in the runtime, JVM languages throw some away in bytecode, and Haskell throws it all away in the compiler.
- 3y ago
- _old_dude_ 3y agoIn C#, value types and reference types are two separate kind of types, value types are pass by copy (like C structs) and generics where clauses have coloring (struct constraints [1]). In Java, value types are immutable so the VM can pass by value or by copy, as a user you don't see the difference and there are backward compatible (per the article), you don't need to recompile the user code when you replace an existing class by a value class. C# has value types since the beginning so primitive types are value types while in Java it seems there will be 3 kinds of types instead of 2. [1] https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters https://learn.microsoft.com/en-us/dotnet/csharp/programming-...
- pron 3y agoFirst, .NET structs are flattenable but they're not value types, which are immutable and so pose further problems. But the two main challenges for adding any new big feature to Java are forward compatibility and simplicity. 1. Forward compatibility: While backward compatibility means that old code continues to run and so is easy to do (add the new feature "on the side"), forward compatibility, or migration compatibility, means making it easy for old code to take advantage of new features with little or no change. 2. Simplicity: For every feature (added to help, say, efficiency) that makes an advanced developer happy you risk scaring away ten less-advanced developers. Complex languages are also rarely taught as first languages, which makes it very hard for them to reach or remain at the very top of the most popular languages. Eventually every language faces requirements that demand a new feature that makes the language more complex, but if great care is not taken to control the added complexity as much as possible (or even give up on the feature as not being worth the cost to complexity), the languages eventually faces a threat to popularity. While Java has always cared about these two, .NET not so much (maybe they're right not to, but in any event that's a real philosophical/cultural difference between the two platforms). For example, to make simple blocking enjoy the scalability benefits of async/await you need to change a lot of things; that wasn't acceptable for us. And MS has always been a fan of rather complex languages (with the exception of VB); as early as the late eighties MS was "the C++" company. Now, to be more specific, one of the big challenges of value types is object initialisation, which is what John's article is primarily about. If you create an array of a value type, the elements must be initialised to some value which isn't null. How do you express that in a way that is relatively forward compatible, simple, but also efficient? Furthermore, as John points out, even for value classes that don't admit a "zero" default value, how do you express the constructor for an immutable type that can be flattened? It's easy to do this for reference records because reference types are always "published" to readers with an atomic write of the pointer, but value types (which, again, are immutable) need to initialise their fields atomically even when flattened. BTW, generic erasure is not a big challenge. While value types will eventually (in a later phase) require specialised generics (because you want, say, an ArrayList of a complex number value type internally use a flattened array), adding that is not a huge problem for forward compatibility and simplicity because value types are invariant, i.e. they cannot subclass nor be subclassed, so the complexity and forward compatibility issues that reification brings to variant types aren't as bad (the problem with reifying generics that can be variant is that you need to bake a particular language's variance strategy into the runtime itself). Erased generics have so far helped Java much more than they hurt it because we keep seeing their forward-compatibility benefits over and over (including for future features that we're thinking about). They also help simplicity because they make the runtime an attractive compilation target for complex languages that may draw those who prefer such languages while keeping them on the platform and at the same time reducing the pressure on the Java to add complex features that could threaten its popularity among the majority who prefer simpler languages.
- pjmlp 3y agoBesides what everyone else is describing, people forget that JVM was designed for Java only, while Common Language Runtime was designed as a polyglot runtime, including support for C and C++, and to this day C++ is officially supported, even if only on Windows (as Visual C++ is the only production compiler with a CLR backend).
- samus 3y agoA non-obvious difficulty is that on the bytecode level constructor invocation are "special", i.e., a separate bytecode command has to be used to call it. Therefore, introducing separate instructions for value types would have required recompilation (or rewriting at startup) of user code that calls constructors of value classes. This would have been an ongoing migration issue and a potential source of bugs, not just because of Java Core legacy like the primitive wrapper types. Fortunately, it turns out that the JVM can be adapted to smoothly hide the difference behind the scenes.