Can someone explain why "in-the-VM" generics design is better than the erasure model of Java?
Can someone explain why "in-the-VM" generics design is better than the erasure model of Java?
You could look at the head of the list to make that determination, (because the List members still have a concrete type), but that isn't a general purpose solution.
Languages like Scala use manifests to preserve the information but it's a major kludge.
- a List<Thing>
- a List<ThingSuperclass>
- a List<? extends Thing>
- a List<? super ThingSubclass>
- a List<IThing>
- a List<ILiterallyAnyImplementedIFace>
- a List<IHaventForgottenInterfacesCanExtendOthers>
- a List<Object>
- a List<I'm probably forgetting some possibilities>
And you have no way of knowing which it is.So yes, it's a boundary, which is significantly more than nothing. But far less useful than it feels like at first.
Idiot.
His two points.
Being able to do type erasure is great because it means your language is coherent. (Don't need to do any nasty kluges in the run time)
And then everyone ratfucks themselves by actually implementing type erasure, and now your language will never have good tooling and thus will die a lonely unmourned death.
He commented that C/C++ has type erasure and it's also a massive kludge (via the elf format). No one will invest that much effort into a new language.
tl;dr: Type erasure, it's great, don't do it.
Imagine wanting to know what the object at address 0x34199920 is. If you have type info[1] you can cross reference the type info with 'generic array' and then do the same with element 2, address 0x34199920 and know it's a 'foo object'.
[1] Say the first 4 bytes is _always_ a type index that the compiler or interpreter generates.
Note that this is not necessarily an issue for very statically-typed languages e.g. if I remember correctly GHC uses erased generics (they don't exist at runtime) which works fine because Haskell has neither pervasive RTTI nor specialisation, so the type erasure has ~no runtime visibility or impact.
I don't think this is correct. In C# code at runtime, and in the bytecode, you can inspect types for metadata about their generic type params. It is present in the .Net bytecode.
See https://docs.microsoft.com/en-us/dotnet/csharp/programming-g...
> When a generic type or method is compiled into (bytecode), it contains metadata that identifies it as having type parameters.
from https://docs.microsoft.com/en-us/dotnet/csharp/programming-g...
Where erasure can cause issues is at runtime. For example:
X isa List<Int>
Even if X is indeed a list of ints that information isn't held at runtime, so the test can't be supported.Having said that in some respects this is a C# / Java ecumenical matter. Haskell, for example, doesn't bake types into output code so I suppose it erases even more that java, but it supports very rich polymorphic behaviour.
Ease of programming: your generic type bindings don't disappear at run-time and can be used run-time logic decisions, basically violating Bracha's law (types shouldn't influence run-time behavior), but I found it incredibly useful for meta-programming reasons.
Is there something special that type erasure adds, that you couldn't have Haskell without it? From a layman's perspective it seems that Haskell could be implemented with either an erased or a reified generics model under the hood, without changing the public surface of the language. But is there something that type erasure enables that reified generics does not?
It's not completely orthogonal as erased generics are annoying/problematic when the language does provide reflection/RTTI.
So you've got 4 (reification x reflection) states 3 of which are fine:
* if you have erasure and no reflection (Haskell) you're fine: you don't have runtime types but they don't matter/are inaccessible
* if you have reification and reflection (C#, C++/RTTI) you're fine: you can access runtime types and have them
* if you have reification and no reflection (Rust, C++/noRTTI) you're fine: you can specialise & discard types at runtime
* if you have erasure and reflection (Java) you're fucked: you can access types at runtime, but many aren't here anymore
> From a layman's perspective it seems that Haskell could be implemented with either an erased or a reified generics model under the hood, without changing the public surface of the language. But is there something that type erasure enables that reified generics does not?
A simpler implementation.
The only benefit to the erasure model is it's easier to implement for the runtime.
Here's a few other examples of the same point, https://twitter.com/jon_cham/status/969929683587432450 and https://twitter.com/headius/status/958371298975080448
And, in the case of F#'s type providers, it means not having to generate zillions of types when coding against some huge schema. But, on the other hand, it does give you the option to do so.
I'm not sure if F# is leveraging the DLR, but there is a lot of fun that can be had there in generating types on the fly, on par with the most dynamic languages out there. It is too bad that it has been nerfed with the UWP/AOT compilation push.
Yep, I knew about that, but type providers don't necessarily generate generic types. So, I meant something a bit different.
As you know, If Foo<T> is an ordinary generic, then T must be a CLR type, which is to say that it must be code that was somehow turned into a CLR type (usually by a compiler such as C#). But, if Foo<> is a type provider, then arbitrary code of an arbitrary language can go in between the angle brackets (which is why type providers can accept strings). Essentially, each type provider is a compiler, but the code that is generated must be expressed (or "wrapped", if you will) as a CLR type. But these types need not be generic (and usually aren't, I think). So, not having erased types in F# is akin to having a compiler that can't generate loops or subroutines†.
†(Perhaps a better analogy is a compiler that can't eliminate tail-calls, since erasing everything to a certain base type (that F# lets you choose) is kind of like reusing stack frames vis-à-vis the analogy)
Huh, I never thought about this restriction, so you can't add a new interface with 'Reflection.Emit', you can only add a class that implements an existing Interface, is that right?
> but unfortunately it requires going outside of the CLR or even the DLR.
By this do you mean writing raw CLR metadata yourself and then somehow injecting it into the running process or something else? Either way, its sounds like an interesting technique, any links to how it's done?
It was abandoned because interop was hard, and interop was Scala's strategy for success
By that count, so does C.
C has types that are only generic in the case of arrays.
There is a difference.
In Java I can write this function
<T> String getTypeOfArray(T[] arr) {
return arr.getClass().getComponentType().getName();
}
and that will return the name of type T at run-time. If I instead of T[] I use List<T>, getComponentType will return null and there is no way to access what type T is at run-time. At compile time List<T> and T[] behave the same, but at runtime the List loses what type if contains.In C I can't write a function with that signature at all because it doesn't support generic functions.
- Boxing/unboxing would not be an issue anymore, so you gain performance.
- Runtime types are enforced: List myList = new List<String>(); myList.add(new Object());
In JAVA, this is ok. In c#, it throws.
IList myList = new List<String>(); myList.add(new Object());
Most of the time when reflection is needed I see something like this instead of just a plain list type.
<T> void foo(List<T> list, Class<? extends T> type) { ... }List myList = new ArrayList<String>(); myList.add(new Object());
this would compile.
It would be nice if mistakes like that were deprecated then eventually fixed, even if over many years/versions.