On Apr 23, 2021, at 8:38 AM, Brian Goetz <brian.go...@oracle.com> wrote: > > ...The fact is that the natural interpretation of a total type pattern is > that it matches null. People don't like this…
This person does. As you point out, this position enables composition of patterns. And composition is the more important goal, compared to preservation of intuitions about edge cases. > > So, given that a total type pattern matches null, but legacy switches reject > null... > > The treatment of the `null` label solves a few problems: > > - It lets people who want to treat null specially in switch do so, without > having to do so outside the switch. Re. composition, it lets the switch be refactored into smaller or larger switches while still managing its own business. No need to bring in extra `if` statements. > - It lets us combine null handling with other things (case null, default:), > and plays nicely when those other things have bindings (case null, String s:). > - It provides a visual cue to the reader that this switch is nullable. > > It is this last one that I think we may have over-rotated on. I’m glad you are bringing this up. > In the treatment we've been discussing, we said: > > - switch always throws on null, unless there's a null label > > Now, this is clearly appealing from a "how do I know if a switch throws NPE > or not" perspective, so its understandable why this seemed a clever hack. I agree with this goal, as a secondary goal. But the hack is not clever if it harms a more important goal. > ...It might be better to rip the band-aid off, and admit how patterns work. (Because that’s the place Java will be in the long run.) > Here's an example of the kind of mistake that this treatment encourages. Good example. > ...The first switch does the right thing; the second will NPE on Foo(null). > And by insulating people from the real behavior of type patterns, it will be > even more surprising when this happens. It’s possible to tweak the code to work around the problem by adding `null,` on the last case. And an IDE could do this. But it’s a sharp edge that comes from the extra interference of `switch` into pattern semantics, which harms the primary goal of composition. > Now, let's look back at the alternative, where we keep the flexibility of the > null label, but treat patterns as meaning what they mean, and letting switch > decide to throw based on whether there is a nullable pattern or not. So a > switch with a total type pattern -- that is, `var x` or `Object x` -- will > accept null, and thread it into the total case (which also must be the last > case.) To me this is the material point, and has been all along: There is never a need to perform an O(N) visual scan of a switch to see if it accepts nulls, since the users simply have to inspect the final (and perhaps initial) case of the the switch. Good style will avoid puzzlers such as final cases which are difficult to classify (total vs. partial). The language does not have to carry the burden of enforcing full annotation. A second point: In the present design, and especially with the cleanup you are proposing, I think there are several possible (optionally selectable) styles of null processing in switches. (Reading later I see you already brought up most of these points.) Style A1 is implicit null acceptance. Pattern totality determines null acceptance, end of story. Style A2 is implicit null rejection, with markup of non-totality using `default`. That is, a coder could choose, as a matter of clarity, to call out partial final cases by adding `default: break;` after them. (E-switches don’t need A2.) Style B1 is explicit null acceptance. If null is allowed by the switch, then the token `null` must be present, either at the head of the switch or just before the final total pattern. Style B2 adds explicit null rejection, by adding something like `case null: throw …`. That’s probably too much noise in most cases. The current draft mandates explicit null acceptance. Brian is suggesting that we allow the other style too. I think it’s a good suggestion. (An extra option would be to allow some bespoke way to annotate totality and/or non-totality of the final case. That interacts with things like enums and sealed classes, so I’ll just mention it and move on.) I think that IDEs can help users pick the style that is correct for them. Bottom line: Trust the users to choose how explicit to be with nulls. More importantly, trust them with compositional notations. > Who is this going to burn, that is not going to be burned by the existing > switch behavior anyway? I think very, very few people. To get burned, a lot > of things have to come together. People are used to saying `default`; those > that continue to are not going to get burned. (Style A2.) > People are generally in agreement that `var x` should be total; people who > use that are not going to get burned. Switches today NPE eagerly on null, so > having a null flow into code that doesn't expect it will result in ... the > same NPE. > > And, people who want to be explicit can say: > > case null, Object o: (Style B1.) > and it will work -- and maybe even IntelliJ will hint them "hey, did you know > this null is redundant?" And then learning will happen! (Now I see I should have read ahead. Oops.) — John