Nulls
Brian Goetz
brian.goetz at oracle.com
Tue Sep 25 07:48:28 PDT 2012
>> Your disbelief is well-founded, since that's not what is being
>> suggested. What
>> we're suggesting is that findAny (or, more precisely, the Optional
>> constructor
>> called by findAny) could throw NPE if the stream contains a null, even
>> if the
>> stream also contains a non-null. So the spec looks more like:
>>
>> @return an absent Optional if the stream is empty, or a present
>> optional containing a selected element from the stream
>> @throw NPE if the selected element of the stream is null
>
> Which does make findAny null-aware. And in a less than helpful
> way, because if a user gets NPE, then they know that the predicate
> DOES hold for at least one element (null).
We could hide behind "its not the op throwing, its the Option", and
compare it to .into(nullHostileCollection). But, even not trying to
hide, I'm not sure its null-aware as much as simply null-inappropriate
-- i.e., don't use this method if you have nulls in your streams --
which means that the user does has to be null-aware. Which I think is
your point -- the user has to reason about it.
> As usual, my main concern is about impact on composition
> (aka modular reasoning). Any general-purpose
> higher-level utility using findAny without knowing
> if the source may include nulls will need to do
> something like:
>
> boolean present;
> T x;
> try {
> Optional<T> r = ...findAny(...);
> if (present = r.isPresent()) x = r.get();
> } catch(NPE ex) {
> present = true;
> x = null;
> }
>
> Not very nice.
Or more nice:
....filter(o -> o != null).findAny();
> I hate to be a pest about this, but the only choices
> I know that compose at all remain:
>
> 1. All stream ops throw NPE on any null element
> 2. All stream ops ignore nulls.
> 3. No use of Optionals; rely only on valueIfAbsent constructions
>
> And of these, choice (2) still seems most defensible.
Not trying to be clever, but I do think that the behavior we are
proposing *is* ignoring nulls. It just that the findAny is fused to
another abstraction (Optional) that is null-hostile.
I could get behind (1), but the only real justification for it at this
point is "eat your vegetables, they're good for you." Which seems a
little heavy-handed.
At this point it seems your primary objection is that because findXxx
(and possibly other methods) are fused to Optional, we have a collision
of worlds, and the user has to keep track of the demarcation between the
null-friendly world and the null-hostile world. The choices are:
- Accept a slightly more complex user model (the user has to reason
about which ops are null-safe)
- Distort Optional to be null-friendly (I think this has been roundly
rejected)
- Eliminate the Optional-bearing ops (Tim would call this punishing
the innocent)
- Distort streams to be null-absorbing (This is your (2) above)
- Make streams null-hostile (your (1) above)
But there's no free lunch -- there's distortion and complexity
everywhere, and the user *still* has to keep track of the demarcation no
matter what we do. (If we choose (2), the user may be surprised to
wonder "where did my nulls go? why did my size change?".)
I guess I still prefer the "make the model slightly more complex to
reason about", as long as we can make all that complexity fall at the
feet of the null lovers -- which the current proposal does. After all,
if you've got nulls in your collection, you *already* have to reason
about "I can use FooList as a target but not BarList". Adding "I can't
use Optional as a target" to that list of things to reason about does
not seem to make it qualitatively harder. And putting all the
complexity on those who have nulls means those who don't use nulls get
everything they want -- including a simple and safe model.
More information about the lambda-libs-spec-experts
mailing list