For example, Imagine you have an api like `void do(List<Foo> foos)`. In the erasure environment of the JVM that looks like `void do(List foos)`. From python it's pretty easy to call with a `foos = [Foo()]`. But not so much if your python implementation needs to figure out how and if it can coarse it's `List` type into a `List<Foo>` type.
Having reified generics in the CLR just lets you store more type information. There isn’t much of a trade off for CLR end-users.
Compare this to the constraints and workarounds that Kotlin and Scala have due to type-erasure on the JVM.
And as far as I'm aware, both kotlin and Scala don't really suffer due to type erasure.
That being said, it is easier to write a language on top of the JVM with good interop, since there are less ways to implement features. Essentially, your language has to interop with Java.
And it is harder to have good interop between CLR languages because there are more ways to implement features. Essentially, your language has to interop with C#.
The creator of Scala disagrees: https://youtu.be/Xn_YpUtXWT4?t=850
The monomorphization of CLR generics is what NativeAOT does, though it doesn't support some C# features.
TypeScript is essentially C#, but with type-erasure and lacking the low-level struct & pass-by-reference features.
I do think the C#/CLR struct implementation is better though.
Nowadays largely abandoned, and I think not everything survived the transition from .NET Framework into modern .NET.
I suppose DLR would be comparable to GraalVM/Truffle.
The difficulty of implementing a dynamically-typed language directly on the CLR and JVM are about the same. Though, it would probably be more efficient on the CLR with access to lower level operations for memory management.
I think an interesting project would be to implement a CIL interpreter on GraalVM.
GraalVM already handles LLVM bitcode, much cooler than plain MSIL.
And here there is another example where Java ecosystem ends up being better.
MSR had a compiler framework similar to GraalVM, called Phoenix, it was going to replace VS, LLVM style, instead it died and what is left are a couple of research papers.
Anonymous classes are still used (sometimes). It simply depends on the circumstance of the lambda.
For example, this will result in a new anonymous class being generated.
void foo(String s) {
stream.filter(i->s.equals(i));
}
The class gets generated to capture the `s` variable. Indy gets used in the `filter` method because the incoming lambda or method reference could be several types of method calls. For example, a constructor, an instance method, or a static method (I believe there are few more in the JVM bytecode).What won't generate a new anonymous class is this sort of lambda
stream.filter(i->"foo".equals(i));
That will generate a new method on the parent class which ultimately gets called. Since nothing is captured it can be directly called without a new instance being created.Never bothered to actually look into the generated bytecodes.
I was wrong, it looks like the case I gave is one which doesn't result in a new anonymous class being generated. Instead the lambda metafactory gets involved to avoid that allocation.
I apparently didn't see what I thought I saw. I thought that I had seen new `lambda.$1` classes being created in call stacks when debugging. Maybe I did, but the lambda was serializable (we have that in some unfortunate places in our code base).
There are still cases where new classes get generated, but that's pretty much just for serialization.
[1] https://cr.openjdk.org/~briangoetz/lambda/lambda-translation...
The DLR is not "abandoned" so much as "complete". Everything did survive the transition into modern .NET and IronPython 3.4.2 runs just fine on .NET 6+ [1]. (For those trapped on the ugly side of the Python 2/3 split, even IronPython 2.7.12 runs on .NET 6.) Most of the "magic" of the DLR is shared with C# Linq in interesting ways (the System.Linq.Expressions AST) and sort of "has to survive" if only to properly support IQueryable<T> (and wilder relatives like IQbservable<T>, the Q is not a typo) even if most people aren't actively using DLR languages today (nor that much usage of C#'s `dynamic` keyword).
IronPython is a community supported (truly open source) language so doesn't get as much attention now as it did in the brief "first party" support era, so some may call that "abandoned" but F# has always lived in that gray space where it is primarily "community supported" more than "officially supported" by a dedicated team at Microsoft.
[1] https://github.com/IronLanguages/ironpython3/releases/tag/v3...
Even though most of the communication around dynamic was about COM support and Excel.
The COM support is by implementing IDynamicMetaObjectProvider in a cross-language reusable base class.
The C# `dynamic` keyword adds some smarts about IDynamicMetaObjectProvider to the C# compiler, but those smarts were never seen to be needed as a low level tool in the CLR itself. And again, once the C# compiles the IDynamicMetaObjectProvider calls, those mostly just result in System.Linq.Expressions for the JIT compiler to optimize in the same way it optimizes other usages of Linq.
This is fine if you hand-roll all your code yourself, but I often use mapping libraries to lower the code footprint and the problems resulting from schema changes are subtle and fly under the radar. This is different from classes with hard construction guarantees, which Java would offer with their "integrity by default" mantra. Where you can opt out of integrity for performance benefits (which is also part of the design).
And Nullability in C# is an absolute nightmare. The type system has completely different rules for nullable types that generalize over classes and structs and there is no generic such as a "Nullable type".
It's just lots of minor annoyances that don't form a cohesive whole.
Structs are values, classes are entities with encapsulation.
The shape of the state would be structural. Whether or not the data in that shape is valid is behavioral.
Structs are useful when working with spans of memory.
Another example of a good usage of struct is Guid, which is 128 bits of data packed together.
The C# equivalent to Java ‘value class’ would be a class with a struct encapsulated for data. The data is flattened and allocated on the heap like Java. Similarly, escape analysis could stack allocate the class at runtime.
Structs in most languages simply bunch a couple constraints together to get another set of performance benefits, but there's no law stating that they couldn't be singled out. In the design of Valhalla, it states that types can come in 4 buckets:
1: Fully identity classes (total control, mutable)
2: Value Based classes (no mutability, but full integrity and dense memory layout)
3: Implicitly constructed values (forced empty default constructor for swift bulk array initialization)
4: Tearable Values (No cross-field integrity during runtime for parallel access)
And I bet that for a vast majority of developers, #4 will come to a shocking surprise, thinking "values are threat safe" because they are told to use immutables.
This way of splitting up structs is the real interesting part of Valhalla, but this shitty AI-generated article buries everything interesting.
You have to opt into force flattening, and then it’s the same as a struct, except it’s still heap allocated without escape analysis. You still have to implement synchronization to prevent tearing.
Static code analysis can give you a warning for potential tearing of structs.
DotNext.Threading provides Atomic<T> to enable high-performance atomic operations on structs without heap allocation.
https://dotnet.github.io/dotNext/features/core/atomic.html
The design of value classes just seems counteractive to its purpose: memory management. If I want to manage contiguous blocks of memory, let me manage contiguous blocks of memory. If I want to allocate something on the stack, let me allocate something on the stack.
The paradigms of struct vs object are too different and they’re trying to combine them into one.
Regarding #4, is this actually a done deal? I haven't dug into the JVM specifics, but I thought they would avoid allocating objects. And for now they just want to get the model right, while continue to optimize as time moves on. I think that's the right approach.
I actually see this way less critical because if you truly have performance-critical usage of structs, you know what you are doing. And if you know what you are doing, you will know about opting-in.
And for everything else? I think it's nice to have a range of benefits that come from having a value type without handing a gun to a monkey. Because the feature will be misused by people that don't know about tearing, thinking "value" is a free performance upgrade. And I do believe that it is the right mental model to reason about it.
I just don't see the huge issue. If the CLR has a way to provide atomic access to non-tearable structs, surely the JVM can too? We are talking CPU instructions here after all. Or am I missing something?