Nullable switch

Brian Goetz brian.goetz at oracle.com
Thu Aug 6 22:14:03 UTC 2020


If we were paying the full cost of nullable types (T?), then there would 
be an obvious choice: T would be a non-nullable type pattern, and T? 
would be a nullable type pattern.  But introducing an `any Foo` notion 
has similar conceptual surface area, but dramatically less utility.  So 
the "return on syntax" for "any Foo" is not good.

We were considering T? at one point, back when we were considering T? 
over in Valhalla land, but as soon as that didn't pan out over there, 
the attractiveness of using it over here pretty quickly went to zero.


The argument for using totality to wriggle out of the nullity trap is a 
sophisticated one, which may be part of the problem, but it is largely 
about uniformity (and partially a pick-your-poison.)

I think this is a forced move: that

     case Box(var o):

be total (including Box(null)).  Any other conclusion is hard to take 
seriously.  If this excludes Box(null), users will make frequent errors 
-- because users routinely ignore null's special behaviors (and we 
mostly want them to keep doing so.)

The next step is a principled one: It's not acceptable for `var` 
patterns to be inconsistent with type inference.  The choice Scala/C# 
made here is terrible, that switching between an inferred and manifest 
type changes the semantics of the match. Super Not OK.  So that means

     case Box(Object o):

has to be total on boxes too.

Another principled choice is that we want the invariant that

     x <matches> P(Q)

and

     x <matches> P(var alpha) && alpha <matches> Q

be equivalent.  The alternative will lead to bad refactoring anomalies 
and bugs.  (You can consider things like this as being analogous to the 
monad laws; they're what let you freely refactor forms that users will 
assume are equivalent.)

Which leads us right to: the pattern `Object o` is total on Object -- 
including null.  (If `instanceof` or `switch` have a rigid opinion on 
nullity, we won't get to the part where we try the match, but it can 
still be a nullable pattern.)

You can make a similar argument for refactoring between if-else chains 
and switches, or between switch-of-nest and nested switch:

     switch (b) {
         case Box(Frog f): ...      <-- partial
         case Box(Object o): ...    <-- total
     }

should be equivalent to

     switch (b) {
         case Box(var x):
             switch (x) {
                 case Frog f:
                 case Object o:     <-- must match null, otherwise 
refactoring is invalid
             }
     }

But if the inner switch throws NPE, our refactoring is broken. Sharp 
edge, user is bleeding.

You're saying that the language is better without asking users to reason 
about complex rules about nullity.  But the cost of this is sharp edges 
when refactoring (swap var for manifest type, swap instanceof chain for 
switch, swap nested pattern switch for switch of nested pattern), and 
user surprises.  The complexity isn't gone, its just moved to where we 
we don't talk about it, but it is still waiting there to cut your fingers.

The totality rule is grounded in principle, and leads to the "obvious" 
answers in the most important cases.  Yes, it's a little more 
complicated.  But the alternative just distributes the complication 
around the room, like the shards of a broken window no one has bothered 
to clean up.



On 8/6/2020 5:35 PM, forax at univ-mlv.fr wrote:
> I've re-read the proposed spec about the nullable switch,
> and it still don't understand why people will think that that the 
> following switch is a nullable switch
>   switch(o) {
>     case String s ->
>     case Object o ->
>   }

It's not clear that they have to.  Right now, switches throw hard on 
null, and yet the world is not full of NPEs that come from switches.  So 
most users don't even consider null when writing switches, and somehow 
it all works out.  The last thing we want to do is grab them by the 
collar, shake them, and say "you are a bad programmer, not thinking 
about nulls in switch!  You must start obsessing over it immediately!"  
Instead, if they're in blissful ignorance now, let's let them stay 
there.  Let's make the idioms do the obvious thing (Box(var x) matches 
all boxes), and then, for the 1% of users who find they need to reason 
about nulls, give them rules they can understand.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20200806/b738370f/attachment.htm>


More information about the amber-spec-experts mailing list