Nullable switch

forax at univ-mlv.fr forax at univ-mlv.fr
Thu Aug 6 23:22:32 UTC 2020


> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Remi Forax" <forax at univ-mlv.fr>
> Cc: "amber-spec-experts" <amber-spec-experts at openjdk.java.net>, "John Rose"
> <john.r.rose at oracle.com>
> Envoyé: Vendredi 7 Août 2020 00:14:03
> Objet: Re: Nullable switch

> 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.
It has the same utility as introducing case null. 
The return on syntax is not good, you're right, but this is true for both case null and case any because their are dual. 

[...] 

> 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.)
You made this arguments several times but i don't understand it, why case Box(var o) has to allow null if there is a case Box(any o) which allow null exists. 

> 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.
The meaning of case Box(String s) should be the same for any switch, and not it may accept null or not. 
I'm able to follow your way of thinking, but the end result is just bad. 

And again, people are not compilers, because you are mixing totality and nullability, is the "case Comparable<?> c" accept null or not in the following code is not an easy question. 
var o = (i == 3)? "hello: 42; 
switch(o) { 
case Comparable<?> c -> 
} 

Worst, it may depends on which JDK/library you are using because you may have add an interface to the implement list of a class in the next version of a library. 

> 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.)
As you said the current semantics of instanceof as a strong opinion about null, so here we have a choice, either we generalizing instanceof so the nullable patterns is allowed or we disallow to use the nullable patterns and instanceof never match null. 
This decison is unrelated to how define the nullable pattern, it can be "any" or it can be "var|Object + totality". 

> 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.
Again, this has nothing to do with how to write the nullable pattern. 
So this argument also works if the nullable pattern uses any. 

switch(b) { 
case Box(Frog f) -> 
case Box(any a) -> 
} 
is equivalent to 
switch(b) { 
case Box(any a) -> switch(a) { 
case Flog f -> 
case any o -> 
}; 
} 

> 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.
No. The refactoring rules works whatever we decide the nullable pattern is. 

> 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.
The obvious answer is not obvious, it's based on the fact that you believe that if someone see case Object o and knows that the case is total, so he will think that this pattern allows null. 
But knowing if a case is total is far from obvious and in the other way, if you do not want a total case to match null, you have no solution. 

Rémi 

> On 8/6/2020 5:35 PM, [ mailto:forax at univ-mlv.fr | 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/20200807/3532a858/attachment-0001.htm>


More information about the amber-spec-experts mailing list