Which, by definition, has nothing to do with generic types.
By the way, this is another reason why erasure is the only sane way of doing this: supporting reification means that you let developers second guess what the compiler already proved for you. Just say no to reification.
1) I do not want the compiler to create extraneous call-site code, bridge methods and other cruft to ensure that a value is really of the declared type (or derived from it). For instance, when you iterate over a (generic) collection, the compiler will insert type checks and downcasts in to the call site code. With reification the collection would be guaranteed type-safe and no extra cruft with incurred overhead needed.
2) Type erasure rules out using primitive types (or value types) as generic type parameters. Reification allows the actual type to be used with generics, regardless of the "kind".
3) Type erasure forces me into the underworld of automatic boxing/unboxing conversions and suboptimal code. Reified generics allows the compiler to generate verify the code at compile time but generate actual optimized (with respect to the type) code when JITting.
4) Type erasure has a large number of contra-intuitive consequences, gotchas. Example: All realizations share the static members, even though they should be separate classes. Type erasure resembles a macro scheme rather than a function that return classes.
There's much more. You are actually the first person I have ever encountered who argue that type erasure is desirable. I can understand type erasure from a compatibility point of view, but I have never seen anyone defending type erasure without the referring to the migration compatibility that was achieved with TE. That includes Neal Gafter, one of the main forces behind generics in Java.
Type erasure was a compromise to allow migration, it is not a design choice that would've been made otherwise.
Scala wouldn't even be possible in its current form if Java had used reified generics.
(Scala type's system is constrained by the need for interop with Java, but that's an unrelated issue.)
No. Having a byte-code interpreter which is essentially an assembly language allows you to create any type system you want. It is wwhen you require interop with an existing type system that you are constrained.
>Otherwise it would be absolutely forced to essentially conform exactly to Java's type system since the targeted bytecode would require all sorts of type annotations which would have to be correct-according-to-java.
One could argue that it would be prudent to base your type system on the type system of the platform you choose. Essentially what Scala chose the route of not integrating deeply with the platform type system. That choice could have been made regardless of how advanced the type system of the platform was.
So if the platform had offered reified generics, Scala could still had chosen to ignore those and gone with type erasure. But they could also have chosen to support reified generics. I'm interested in what concrete constraints that would have imposed on Scalas type system, except for interop with Java.
Ironically, the chosen design has forced Scala into adorning with all sorts of type annotations to ease interop with Java: It has to lie about the generic type argument the same way Java does in the type metadata, i.e. "consider is a collection of Shapes. Note: it may be other object types so you must perform downcasts yourself*".
Not with anywhere near decent performance.
#2: No it doesn't. You can still specialize as appropriate when you know the types at compile time, see e.g. miniboxing for Scala.
#3: This was added by the Java language designers for convenience. It would be possible to require explicit casting/specialization, but they deemed it too verbose. Of course then we ended up with advice like "explicitly unbox" as seen in Effective Java...
The last bit is an appeal to authority. There are many people, esp. in the Scala community, who argue convincingly against reification.
It can optimize some of the cases. Problem persists and is now even less predictable.
> No it doesn't. You can still specialize as appropriate when you know the types at compile time, see e.g. miniboxing for Scala.
Yes it does. The subject here is generics in Java, not scala kludges to compensate for the poor generics design.
> This was added by the Java language designers for convenience.
Strawman. You can point to any language feature and call it convenience, basically anything above a Turing Machine is convenience. The fact is that they realized that the type erased generics would appear severely hampered if they could not be used for the most used types at all. Hence, they swept the problem under the rug, by allowing implicit (automatic) conversions. The problem is still there, there's a big bulge on the rug and the linked FAQ is essentially a map to guide you around it so that you do not trip.
> The last bit is an appeal to authority. There are many people, esp. in the Scala community, who argue convincingly against reification
I'd be interested in hearing those arguments, specifically those that do not invoke interoperability with Java. Have source?
My source: http://gafter.blogspot.dk/2004/09/puzzling-through-erasure-a...
The clear way to do this is to put them in a Map<Class<T>, Handler<T>>. Now how do these Handler objects declare that they're ONLY able to handle a specific Foo subtype (let's call it FooBar)? It'd be REALLY nice if I could just say "Hey handler, what's your generic type? Is the message I just got an instanceof your generic type?" Java won't let you do this due to erasure.
Okay we can get around this. Each Handler<T> has to declare a method (say getType()) that returns the Class<T>. Since I'm generically declaring my class FooBarHandler<FooBar>, I can protect myself from returning a BazBat in this method, but there's NO way for me to abstract this method away. Each Handler has to declare a "public Class<T> getType()" that returns T.class. But since T isn't a thing past compile time, I have to repeat this same method implementation for each concrete type. Gross. This has the added irritation of forcing all the parent classes, if they implement this interface, to be abstract, since they can't implement the method appropriately. This isn't necessarily a bad thing, but the limitation is annoying.
In an ideal world, I can declare some ParentHandler<T> that has this method, and have all my handlers just extend it, with no duplication.
No, it wouldn't be nice, it would be unsafe. If one day you end up adding a new type in your container, you need to update your runtime check as well or your code will fail in mysterious ways.
Erasure keeps you honest by asking you to think carefull about the types so that they can be checked by the compiler, and once the compiler has done this verification, you are guaranteed that your code will work.
Any language feature that encourages the use of reflection , such as reification of types, should not be supported by a language that wants to claim to be sound.
If the compiler provides a couple things:
data TypeRepr
typeReprEq :: TypeRepr -> TypeRepr -> Bool
typeOf :: Typeable a => a -> TypeRepr
such that TypeRepr cannot be generated (e.g. faked) by users, typeOf is guaranteed to be genuine, and (this is the hardest) such a thing doesn't violate parametricity then you can use that interface to write safeCoerce :: (Typeable a, Typeable b) => a -> Maybe b
which is guaranteed to only allow the coercion when `a` and `b` actually happen to be the same type.Upon thinking about it a bit, this problem seems to be functionally equivalent to pattern matching on the type of the message (which Java also lacks). I'm not a Haskell guy, but the immediate solution I'm seeing is to just have separate cases for each one. This is still an inferior solution in this particular case, because it forces you to modify the code in two places; luckily the type system helps here and makes sure you do.
But consider a problem of a different form:
Imagine I have an object Bar<S , T> and I want to have a frobnicate(S s) and a frobnicate(T t) method. Since Java erases, I can't do this. frobnicateS and frobnicateT it is! This is particularly annoying because you know S and T are different, but you can't express it to the type system! This seems like it'd be solvable by some sort of disjoint union, but again, Java lacks this. Fun fact, it does have a way to bind a generic on multiple classes: Bar<T extends Baz & Bat>. It would be natural to add a | for this sort of operation, if they ever figured out how to do this sort of operation.
I don't think the same erasure problem is actually a consequence of type erasure - it is just defined in the spec.
Your Frobnicate example is interesting since you want an extra level of polymorphism in there, but getting this can be bad for inference (see MultiParamTypeClasses without functional dependencies). In any case, it's not clear why you ought to expect it to work.
There are better ways of resolving type arguments, such as on Handler implementations, at runtime.
There's no (good) way of going "this is a Comparable<Foo> and a Comparable<Bar>, but not a Comparable<Baz>" that's detectable at runtime. (Also true with other interfaces.) Especially for trait interfaces.
There are also nasty cases where you end up crashing randomly somewhere else in your code at runtime because somewhere something passed a List<Foo> and it expected a List<Bar>, but somewhere in between the type information got lost.
Ditto, I find myself wishing to be able to do instanceof T / T[] (that's an array of T) annoyingly often.
Look at the mess that is EnumMap for an illustration of why type erasure can be frustrating. The amount of reflection done at runtime for what should be (and is, if you write a non-generic implementation, or if you're using a language that's sane) purely compile-time work is... frustrating. (And while you're at it, the fact that you can even attempt to pass in a non-enum into EnumMap. I thought Java was supposed to try to push such errors into compile time?)
It's a shame that various libraries/frameworks have resorted to building APIs around Type Tokens - where users have to create anonymous classes - to create something that effectively reifies some generic type information. It's a lame solution, but a solution.
No, the issue was a much more esoteric one (and an invented and self-inflicted constraint by the designers themselves) of migration compatibility. Consider entity A using compiled libraries (no source available) from vendor R and from vendor S in the same product. Vendor R releases a new version of his library and uses the fancy new generic collections - and refuses to maintain a non-generic version. Vendor S releases a new version of his library but refuses to provide a version that use generic collections. Library S calls code in library R. All of these need to be in place to require migration compatibility.
> its why you could have Scala which its richer type system and a good interop story with Java, whereas the attempt to provide the same thing on .NET faltered in large part because of the .NET platforms reification of generics.
Do you have a source for this? In my view it is the opposite: Reification is the right solution as it makes reified types part of the first-class system with no strange corner cases. It is type erasure that treats realized generic types as second-class citizens, with strange constraints bubbling up through the entire system.
There's a lot of second-hand discussion of this readily locatable on Google, but I haven't saved and can't readily find the posts from people actually involved in implementing Scala on .NET that I saw years ago that directly pointed to this as a problem.