Nullable types and inference
Brian Goetz
brian.goetz at oracle.com
Mon Apr 29 22:28:05 UTC 2019
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.
More information about the valhalla-spec-observers
mailing list