Received on the -comments list.

Indeed, we're well aware of the "action at a distance" concern you raise.

Zooming out, we have three broad choices for addressing the null-hostility of switch as we generalize the switch feature to support patterns:

 - Abandon the existing switch construct for dead, and make a new one with similar but different semantics (sometimes called "snitch");  - Accept the current limitations of switch, and propagate them foreward, potentially creating new anomalies (such as the inability to refactor if-else chains to switch);  - Partially rehabilitate the old feature, looking for ways to slot the new functionality in between the obstacles of the old constraints.

All of these have pros and cons, and within each, there are sub-approaches with pros and cons.  We struggled with the same set of issues with expression switch; there, the main legacy constraint there was partiality (switch statements are partial, but expressions must be total.)  Again, we had the same basic three directions (with many sub-directions), and concluded that rehabilitating switch was better than either having both "switch" and "snitch", or allowing partial switch expressions which would throw at runtime.  The cost there is that you have to look more closely at whether a switch is used as a statement or expression to determine whether the cases cover all the possibilities.

With patterns, and with nullity, we again prefer the rehabilitation path, and down that path, have explored several variants, such as separate pattern denotations for non-nullable and nullable (e.g., T vs T?), modifiers ("nullable-switch"), etc. The con of the path we have taken, as you note, is that (especially if you combine multiple features, like type inference and pattern switch), it is not as obvious whether a pattern is total or not.

Of course, if you want to make it more obvious, you can write more explicit code.  You can, for example, refactor

switch (o.get1().get2()) {
    case Integer i: ...
    case Number n: ...
}

into

Number t = o.get1().get2();
switch (t) {
    case Integer i: ...
    case Number n: ...
}

and now it is more obvious again.

One thing that leads me to consider this as the least-bad alternative is that switches throwing NPE today are not a serious problem, nor do developers universally have to wrap a null check around switches to make this so.  Which suggests that this particular edge is not the sharpest, so making it even less sharp (though more irregular) is unlikely to cause serious pain.

And, it's not like there's a pain-free approach here; there's only tradeoffs that move it around.  Just like with everything else regarding null.  But, we hope the preview process will either validate, or invalidate, this hypothesis -- I think we've gotten as far as we can go with looking at it as a theoretical problem.

Cheers,
-Brian


-------- Forwarded Message --------
Subject:        Observation about nulls in type patterns
Date:   Sat, 25 Jul 2020 21:51:37 +0200
From:   Jens Lideström <[email protected]>
To:     [email protected]



Brian Goetz posted a write-up of the feature Types patterns in switch in
amber-spec-experts on Jun 24:

https://mail.openjdk.java.net/pipermail/amber-spec-experts/2020-June/002235.html

I'd like to contribute an observation about the consequences of the
proposed mechanism for handling null values in switches:

If I understand the proposal correctly the behaviour of null values in a
switch will depend on the type of the expression being switch upon. This
is potentially non-local information, for example when the expression is a
field access or a method call.

To understand the behaviour of null values in a switch readers will have
to examine the source of the expression being switch upon. Also, if the
type of a method or a field is being narrowed or widened that change might
silently affect the behaviour of existing switches in other parts of the code.

Example:

switch (o.get1().get2()) {
    case Integer i: ...
    case Number n: ...
}

If get2 is some method that is declared to return Number then the second
pattern is total and will accept null; if get2 is declared to return
Object pattern the IS total and WILL accept null.

The expert group is probably aware of this fact already but I think it
deserves to be noted explicitly.

Best regards,
Jens Lideström
Random Passer-by

Reply via email to