Feedback on nulls in switch

Stephen Colebourne scolebourne at joda.org
Mon Aug 10 23:02:57 UTC 2020


On Mon, 10 Aug 2020 at 19:45, Brian Goetz <brian.goetz at oracle.com> wrote:
> Of course we have to be compatible with the existing semantics
> of these constructs, and this creates constraints and distortions, but
> the motivation here is as you ask -- moving towards a more consistent
> overall story.  And that story is: nulls are OK to pass around, they
> should only blow up when you dereference them.

Lets agree to disagree on tackling nulls in Java. Lets also say that
I'm willing to accept that there is nothing I can do at this point to
stop nulls flowing through pattern matching to some degree. Most of my
email (the part that was snipped) was feedback on how the pattern
`Object o` simply has no business matching null (however clever or
consistent that seems at the theoretical level).

Given:
  box instanceof Object o
o is non-null. And given:
  switch (box) {
     case Box b:
  }
b is non-null. Yet, given
  switch (box) {
     case Box(Chocolate c):
     case Box(Frog f):
     case Box(Object o):
  }
we are being asked to accept that c and f are non-null and o is
nullable. This simply makes no sense. The pattern `Object o` should
have the same meaning wherever it appears, and that meaning is "is it
an instanceof Object" (and null is not an instanceof Object).

It is even worse because where Box has a top generic type that isn't
Object, I'm expected to know using information that is not present
locally as to whether s is nullable or not:
  switch (box) {
     case Box(Circle c):
     case Box(Rect r):
     case Box(Shape s):
  }
s is non-null if Box generics erase to Object, but s is nullable is
Box generics erase to Shape. A fact that can also change with separate
compilation. (Changing from "extends Shape" to "extends Object" would
change the logic of pattern matching statements in other parts of the
system).

In my first email I presented the `where instanceof` refactoring
catalog argument, which I think is pretty clear. Here is a different
way to write it:

switch (box) {
     case Box b: {
       if (b.value instanceof Chocolate c) ...
       else if (b.value instanceof Frog f) ...
       else if (b.value instanceof Object o) ...
       else ....
  }
Note how we need a separate else clause to cover null. That is what is
needed here - explicit opt-in to accepting null.

IMO the proposed semantics are simply not appropriate. You cannot
expect code readers to know the erased generic type of the type being
switched on, and rationalise that into whether a specific pattern
matches null or not. If a developer uses an explicit type in a pattern
they should get back a non-null instance of that type. Because that is
what they explicitly asked for. End of.

That leaves open the possibility of having `case Box(var v):` match
null and `case Box(Object o)` not match null (like Scala/C#). This
does have some appeal because the code reads acceptably and the
separate compilation / erased generics issue is less significant. The
examples (in other threads) seem to deliberately use `var v` instead
of `Object o` to make the case for accepting nulls. Some of those
examples would not look nearly as good if `Object o` was used instead.
Having said all that, I am also uncomfortable about having `var` not
be simple type inference. But given the choice, it is much more
acceptable for `var v` to match nulls and `Object o` to not match than
to continue with the current proposed semantics.

So, my preference ranking would be:
* explicitly add syntax for nullable (`Box(any v)` for example, or
maybe just `Box(v)`).
 *which is 100 times better than
* `var v` matches null but `Object o` does not
* which is 1,000,000 better than
* `var v` and `Object o` (or whatever the erased type is) match null

Stephen



Stephen


More information about the amber-dev mailing list