Feedback on nulls in switch
Stephen Colebourne
scolebourne at joda.org
Mon Aug 10 09:48:16 UTC 2020
I wanted to provide some feedback on what I would expect by looking at
some code samples being used when discussing nulls in pattern matching
switch. Having read and re-read various mails on the topic, I'm
unclear as to why there is a requirement to allow `var v` to match
nulls (and thus for v to be null). This is significant, as it seems to
drive the requirement for `Object o` to match null and be non-null.
record Box(Object value) { }
Box box = ...
switch (box) {
case Box(Chocolate c):
case Box(Frog f):
case Box(Object o):
}
Given the above, I would expect c, f and o to be non-null. If that
means the switch is not total, then so be it. Deriving from existing
knowledge, and future refactoring yields :
case Box b where b.value instanceof Chocolate c:
case Box b where b.value instanceof Frog f:
case Box b where b.value instanceof Object o:
Which clearly implies non-null o.
I can't see how I would be expected to *read* `case Box(Object o)` as
total and accepting of null. I'm sorry, I know its a clever solution
to the null/totality problem, but I really can't read it that way - it
is far too magical. And the fact that it isn't just Object, but
whatever the top generic type is makes this even worse.
If the last line were
case Box(var v):
I would still expect v to be non-null. Although the instanceof
refactoring shown above doesn't work, this still doesn't *read* like
it should accept null (and yes, even though `var` as a local variable
does accept null, I think the context here implies it to be non-null).
Note that `Object o` and `var v` being non-null meets the test that
both patterns should mean the same, just the opposite meaning wrt null
than the current proposal.
And given this:
case Coord(var x, var y, var z)
I still would only expect it to match if all values were present and non-null.
I don't have a huge problem with constructs like these:
case Box(any a):
case Box(var? a):
case Box(var|null a):
case Box(Object|null a):
case Box b where b.value == null
case Box(opt<var> a): // see below
but I'm unconvinced they are actually needed at all.
A key part of this is that a null v or o isn't really very useful -
all you could actually do with it is check whether it is null or not
in the next statement. So why is there such an effort to get something
that isn't actually useful? Being able to match that it *is* null
might be useful such as to display an error, but matching that it is
Object or null isn't really going to be that useful as those are two
separate logic clauses. And just like instanceof, patterns are going
to shine when extracting actual (non-null) values for logic
processing.
FWIW, I don't have a problem with `case null` at the start of a
switch, although I would question how necessary it really is.
More generally, I think this debate relates to others in records and
Valhalla. It indicates to me that a more overarching story on nulls
would be beneficial. Each separate change appears to be treating null
as an awkward afterthought. For example, given a Valhalla future where
Optional (or `opt`) has zero performance cost, it seems utterly
unnecessary for records to allow nullable components (just declare the
field as the non-nullable opt<String>). And if you took away nullable
fields in records, much of the debate on nulls in pattern matching
would simplify. Similarly, if every Valhalla inline type also
specified a field that is zero when empty inside `opt`, the need for
nullable reference may well reduce/disappear (that deserves a separate
email to Valhalla).
Stephen
More information about the amber-dev
mailing list