----- Original Message ----- > From: "Brian Goetz" <brian.go...@oracle.com> > To: "Tagir Valeev" <amae...@gmail.com>, "amber-spec-experts" > <amber-spec-experts@openjdk.java.net> > Sent: Lundi 6 Septembre 2021 15:46:19 > Subject: Re: Reiterate total pattern accepting null in switch
> On 9/6/2021 5:12 AM, Tagir Valeev wrote: >> Hello! >> >> Now, as we develop support in IntelliJ, we have a little bit of >> experience with patterns in switches. So far, the thing I dislike the >> most is that the total pattern matches null in the switch. I shared my >> concerns before and now they are basically the same, probably even >> stronger. Note that I'm not against total pattern matching null in >> general (e.g., as a nested pattern in deconstruction). But I still >> think that matching it at the top level of the switch is a mistake. >> Mentally, the total pattern is close to the default case. > > I think there are several issues here; let's try to tease them apart. > > The main problem, as I see it, is not one of whether we picked the right > default, or whether that default is unfamiliar (though these are both > valid things to discuss), but that we are putting the user to a > non-orthogonal choice. They can say: > > case Object o: > > and get binding and null-matching, or > > default: > > and get neither binding nor null-matching. > > In some way, you are saying that there is a significant contingency > where users want binding but not null-matching, and we're forcing users > to take a package deal. As a thought experiment, how differently would > you feel if we had both nullable and non-nullable type patterns: > > case String! s: > case String? s: > > If we had the ability to refine the match in this way, then the choice > of binding and null handling would be orthogonal: > > case Object! o: // binding, no null > case Object? o: // binding, null > default: // no binding, no null > null, default: // no binding, null > > So the first thought experiment I am asking you to do is whether, in > this world, you would feel significantly differently. There is no binding available for "default" but the selector value of the switch is available. switch(anObject) { default: // no binding but one can use 'anObject' directly } so i'm not sure to fully understand the point your are trying to make. > >> Also, >> adding a guard means that we do not receive null at this branch >> anymore which is also confusing. > > Good point, I'll think on this a bit. yes, if there is no default, the type pattern for a case which is not the last and the type pattern for the last case has a different meaning (for the record i'm fine with that), but combined with the current rules for the guarded patterns, this has some puzzling side effects. By example, using a guard that is simple enough for a human to see it is always true but not simple enough for the compiler so it is not constant folded, switch(x) { ... other cases case Object obj && x == x -> ... // exclude null case Object obj -> // null goes here } > > To reiterate the motivation, the thing we're going for here is the > consistency that a switch of nested patterns and a nested switch are the > same: > > case Box(Foo f): > case Box(Object o): > > is the same as > > case Box b: > switch (b.get()) { > case Foo f: > case Object o: > } > } > > If we treat the null at the top level, we get a different kind of > asymmetry. What we're banking on (which could be wrong) is that its > better to rip off the band-aid rather than cater to legacy assumptions > about switch. And also we want to keep the legacy semantics of "default", we do not want the existing "default" inside the switch on String/Enum to have a different behavior from the existing ones. > > I think what you are saying here is that switch is so weird that it is > just a matter of pick your asymmetry, and the argument for moving it to > the top level is that this is weirdness people are already used to. > This may be true, though I worry that it is just that people are not > *yet* used to nested switches, but they'll be annoyed when they get bit > by refactoring issues. And there is also actually no way to write "case var o" which is IMHO the explicit way to convey the fact that the last case is total. Using "case var o" in all the examples above make the codes readable. I think the real issue here is that we are in the middle of introducing the different patterns, so the big picture is not in front of us yet. [...] Rémi