Like I said, I sympathize with your position; you're saying "do we
really need this?", which is the right question to be asking. But, I do
believe you're wishing away a lot of stuff in the desire to get to the
answer you want, and that's where we disagree. I would be happy to find
another way to get there, if there is one, but "pretend the problem
doesn't exist" doesn't sit very well right now.
More inline.
The first case is a corner case and for generics, it works until someone try to
stuff null into a value type.
So instead introducing nullable value types in the language which make the
language far more complex than it should be, i think we should come up with a
far simpler proposal, to have a declaration site tagging of the method that
doesn't work with value types.
// proposed syntax
interface Map<K, V> {
"this method doesn't work if V is a value type" public V get(Object o);
}
We explored this idea in M3; we jokingly called this “#ifref”, which is to say,
we would restrict members to the reference specialization. This was our first
attempt at dealing with this problem. We gave up on this for a number of
reasons, not least of which was that it really started to fall apart when you
had more than one type variable. But it was a hack, and only filtered out the
otherwise-unavoidable NPEs.
More generally, there’s lots of generic code out here that assumes that null is
a member of the value set of any type variable T, that you can stuff nulls in
arrays of T, etc. Allowing users to instantiate arbitrary generics with values
and hope for no NPEs (or expect authors of all those libraries to audit and
annotate their libraries) is not going to leave developers with a feeling of
safety and stability.
To get a NPEs when you store something in an array of T, you need the array to
be an array of inline type,
and currently it's not that easy because T is not reified.
Or, return a null from any method that returns a T. And this is the
problem; in order to safely write such generic code, this has to be a
property of the _method declaration_, not just something the
implementation doesn't happen to do right now. Which brings us to either:
- Use nullable types at the instantiation, or
- Force all generics to declare their nullability / non-nullability
Further, allowing users to instantiate an ArrayList<Point> — even when the
author of ArrayList proposes up and down (including on behalf of all their
subtypes!) that it won’t stuff a null into T — will cause code to silently
change its behavior (and maybe its descriptor) when ArrayList is later
specialized. This puts pressure on our migration story; we want the migration
of ArrayList to be compatible, and that means that things don’t subtly break
when you recompile them. Using ArrayList<V?> today means that even when
ArrayList is specialized, this source utterance won’t change its semantics.
yes, you're right, but the downsize is that you have introduce V? in the type
system and everybody has no to think if he should choose V or V? for every
types.
Yes, that's the cost. And it's actually worse than you say, because on
Day One, you will only be able to say
new ArrayList<Point?>
and not
new ArrayList<Point>
and users will bitch and moan about "why can't the stupid compiler
figure out what I want", because they are unaware of the _future_
ambiguities they would be subject to if they did that.
The alternative is to merge L10/L100, and no one gets values for a long
time more, which also sucks.
Essentially, erased generics type variables have an implicit bound of “T extends
Nullable”; the migration from erased to specialized is what allows the
declaration to drop the implicit bound, and have the compiler type-check the
validity of it.
We don't need V? if generics are fully reified so introducing V? 20 years from
now will look stupid.
There's a kernel of a point here, but you've overstated it by a hundred
million billion percent :)
V? remains useful, but given the choice between parameterizing over V or
V?, _most of the time_ users will probably choose V. That doesn't mean
V? is stupid; it's just not the most common case. Which is _exactly why_
we gave "V" the good syntax and "V?" the less good syntax, so that when
we get to that world, we won't have chosen the wrong default (again.)
We have four choices here:
- Don’t allow erased generics to be instantiated with values at all. This sucks
so badly we won’t even discuss it.
- Require generics to certify their value-readiness, which means that their type
parameters are non nullable. This risks degenerating into the first, and will
be a significant impediment to the use and adoption of values.
but nothing break in this mode.
Nothing breaks, but values can't be used with most generics for the next
ten years. That leads to "value types are useless", wich we don't want.
- Let users instantiate erased generics with values, and let them blow up when
the inevitable null comes along. That’s what you’re proposing.
until people annotate the method/class that doesn't support value type.
Sorry, but I can't take this option seriously. Imagine all the generic
code in all the world. Now imagine all the classes that might, in some
case, cause a null to show up in a T. (This includes every generic
interface, as there is always a potential subtype that returns null from
a T-bearing method.) Call this latter category B (for Bad). Now: what
percentage of B do you think will be prperly annotated after a month? A
year? Ten years? 99.9999%? No. 99%? No. Probably closer to 10%.
Which reverts to: "try it and see if it blows up." This is not the safe
programming experience we want people to have.
- Bring nullity into the type system, so that we can accurately enforce the
implicit constraint of today’s erased generics. That’s what I’m proposing.
which doesn't scale because your asking all your users to think as API
developers, not something i want to do when i just want to use an API.
So far, this is the only acceptable approach we've found. If you can
find a better one, we're all ears!
I believe that the issue is that V? should work as a box and currenty
V? is to powerful/useful as a box so people will start to use it as a
true type.
This is a valid discussion (start a new thread). Basically: we've
gotten rid of boxes because they are semantically sketchy (accidental
identity) and harder to optimize, but you're saying: people understand
boxes, they don't understand this T or null thing? Fair point: let's
discuss this (on a new thread.)
- V? should not be called V?, it's to short, you should have some ceremony that
show you that V is the real deal, V.box was better
If it really were a box, then V.box was better. But it is not like the
boxes that people understand, so V.box is an utterly terrible name for
the semantics we have (and people's understanding of boxes.) But, we
are open to another way to write it, if you dislike V? so much.