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.
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.