I like the special bonus semantics, a switch need to specify the null behavior explicitly while it's pragmatic to lees sub-pattern to be total on null.
Because people will ask, if a record has a null record component, the "Right Way"(TM) to not propagate null is to create different patterns like Optional (Optional$$of()/Optional$$empty()) to avoid to bind null. I don't think we should support the fallthrough with null given that adding a "null comma" in front of a case provides the same semantics. Rémi ----- Mail original ----- > De: "Brian Goetz" <brian.go...@oracle.com> > À: "amber-spec-experts" <amber-spec-experts@openjdk.java.net> > Envoyé: Jeudi 21 Janvier 2021 19:52:39 > Objet: Nullity (was: Pattern features for next iteration) >> - New nullity behavior (including case null) > > I think we can refine this item a bit, now that we've made some progress > on guards. There are still some mumbles of discomfort regarding the > treatment of nulls. We are never going to quiet those, because nulls > are a persistent source of thorns, but I think we can add something new > to the discussion in light of recent progress. (But please, let's not > just recycle old arguments.) > > There are three contexts so far where we can put patterns: > > - RHS of an instanceof > - case label of a switch > - Nested inside a record or array pattern > > Some of these have strong opinions on nulls, that might cause action to > be taken before the pattern is even tested. This is fine. But we > should be clear about which behaviors are part of the _construct_, vs > which are part of pattern matching. Currently: > > - instanceof always says false on null, no matter what > - switch throws on null, except under circumstances we are defining now > - nesting has no null opinions, but when the inner pattern requires a > dynamic test (i.e., is not total), that pattern may have an opinion > > With regard to what it means for "Pattern P to match target X whose > static type is T", previous rounds came to a pretty solid conclusion > that `var x` and `Object o` _must_ match null. (So please, let's not > reopen that unless there is something significantly new to add.) > > What is missing is: when `Object o` in some context matches null, how do > we express that we actually wanted to exclude null? We've explored in > the past some sort of `Object! o` type pattern, but resisted this for > obvious stewardship reasons. But the goal is valid: the pattern matches > too much, and we want to refine the match. And, this is what pattern > composition does, so we should be looking to pattern composition to > solve that. > > Here's what is new: the treatment of guards as composable patterns. With > that, we can write a "non-nullable" nested pattern like: > > case Foo(Object o & false(o == null)): > > or > > case Foo(Object o) & false(o == null): > > (depending on where the user thinks the null check is better.) What I > like here is that we haven't distorted the meaning of patterns to handle > null specially, but instead are using ordinary composition mechanisms to > allow users to filter nulls just like any other bad value. > > If it turns out, that the world becomes full of such locutions, we can > consider (in the future) adding a "null guard" pattern, `null(o)` and > `non-null(o)`, at which point the above becomes: > > case Foo(Object o & non-null(o)): // guard flavor > > or > > case Foo(Object o & non-null()): // targeted flavor > > but we surely don't have to do that now, as this is just a trivial > syntactic shortcut. > > So, not only don't I think we have to add anything new now for handling > nulls, or further distort the semantics of matching, but I see this as a > dramatic validation of the guards-as-patterns strategy. (Who knew > composition was powerful!) > > > #### Special bonus nullity discussion > > Separately, another possibility for switch is to slightly refine the > null-handling of the switch _construct_ (not patterns), in a way that > some people might find less surprising. > > Problem: Switch has always been null-hostile, and pattern totality is > subtle. People seem very afraid (more than I think they should be, but > fine) that the nulls will creep into the total patterns without warning. > > Currently, we've defined that switch is null-hostile _unless_ there is a > nullable case, and the two nullable cases are `case null` and `case > <total pattern>`. Obviously no one will be surprised to see a null > match `case null`, but they might be surprised at the latter. > > So the alternative idea to explore is: make `case null` the _only_ > nullable case, but relax fallthrough to allow falling through from `case > null` to a type pattern, and adjust the binding rules to make this make > sense. Then, a switch is only nullable if there is a case null (easy to > spot) and a type test only sees null if the `case null` is highly proximate: > > ``` > switch (o) { > case Object o: // still NPEs on null, since no case null > } > > switch (o) { > case null -> doSomething(); // nulls here > case Object o -> doSomethingElse(); // no nulls, already handled > } > > switch (o) { > case null: // fall through > case Object o: // nulls fall into this case, and are bound to o > doSomething(o); > } > ``` > > As a bonus, it works not just for total type patterns, but lets us sort > the nulls into any type pattern: > > ``` > switch (o) { > case String s -> ... > case null, Integer i -> ... // nulls go here > case Object o -> ... > ``` > > I'm OK with this because it seems a reasonable accommodation for the > historical null-hostility of switch, but doesn't affect the semantics of > patterns at all.