> De: "Brian Goetz" <brian.go...@oracle.com> > À: "Guy Steele" <guy.ste...@oracle.com> > Cc: "Remi Forax" <fo...@univ-mlv.fr>, "Tagir Valeev" <amae...@gmail.com>, > "amber-spec-experts" <amber-spec-experts@openjdk.java.net> > Envoyé: Vendredi 21 Août 2020 17:14:57 > Objet: Re: [pattern-switch] Exhaustiveness
> Yes, this is the sort of ordering I was aiming at. >> If the user does not want such implicit handling of an optimistically total >> situation in a statement switch, then it is always possible to provide >> explicit >> clauses “case null: break;” and “default: break;”. > Indeed, and this is why I was trying to break it down into a set of cases, to > ensure that there always is a pattern the user can denote if they want to > catch > some part of the residue. Where we are now is: > - In a total switch (currently just switch expressions), any residue involving > novel values gets ICCE, a null gets NPE, and any residue not in the above > categories gets (something, maybe NPE, maybe something else.) > - If the user explicitly wants Box(null), they have two choices: explicitly > match Box(null), or, more likely, use some total pattern on Box (`Box(var x)`, > `Box b`, etc.) Similarly, if they want (for whatever reason) Box(novel), they > can similarly use totality. (I hope people are beginning to see why totality > in > nesting is so critical.) > So, next sub-subject (sub-ject?): when, and under what conditions, do we get > NPE > from non-total switches? I said this yesterday: >> Separately (but not really separately), I'd like to refine my claim that >> `switch` is null-hostile. In reality, `switch` NPEs on null in three cases: a >> null enum, String, or primitive box. And, in each of these cases, it NPEs >> because (the implementation) really does dereference the target! For a >> `String`, it calls `hashCode()`. For an `enum`, it calls `ordinal()`. And >> for a >> box, it calls `xxxValue()`. It is _those_ methods that NPE, not the switch. >> (Yes, we could have designed it so that the implementation did a null check >> before calling those things.) > I bring this up because these situations cause current switch to NPE even when > the switch is not total, and this muddies the story a lot. We can refine this > behavior by saying: "If a switch *on enums, strings, or boxes* has no nullable > cases, then there is an implicit `case null: NPE` at the beginning". > In other words, I am proposing to treat this "preemptive throwing" as an > artifact of switching over these special types (which is fair because the > language already gives these types special treatment.) Then, we are free to > treat residue-handling as a consequence of totality, not a general > null-hostility of switch. > Let me repeat that, because it's a big deal. > Switch is *not* null-hostile. We were just extrapolating from too few data > points to > see it. > Switches on _enums, strings, and boxes_, that do not explicitly have > null-handling cases, > are null-hostile, because switching on these involves calling methods on Enum, > String, > or {Integer,Long,...}. > If you put a `case null` in a switch on strings/etc, it doesn't throw, it's > just > matching > a value. > In all other cases, null is just a value that can be matched, or not, and if > the > switch ignores its residue, the nulls leak out just like the rest of it. > In the general case, switches throw only when they are total; for partial > switches > (e.g. statement switches), null is just another value that didn't get matched. > I believe this restores us to sanity. I'm not hostile to that view, but may i ask an honest question, why this semantics is better ? Do you have examples where it makes sense to let the null to slip through the statement switch ? Because as i can see why being null hostile is a good default, it follows the motos "blow early, blow often" or "in case of doubt throws". Rémi [...] > On 8/20/2020 9:02 PM, Guy Steele wrote: >>> On Aug 20, 2020, at 6:14 PM, Brian Goetz [ mailto:brian.go...@oracle.com | >>> <brian.go...@oracle.com> ] wrote: >>> I suspect there are other orderings too, such as "any nulls beat any >>> novels" or >>> vice versa, which would also be deterministic and potentially more natural >>> to >>> the user. But before we go there, I want to make sure we have something >>> where >>> users can understand the exceptions that are thrown without too much >>> head-scratching. >>> If a user had: >>> case Box(Head) >>> case Box(Tail) >>> and a Box(null) arrived unexpectedly at the switch, would NPE really be what >>> they expect? An NPE happens when you _dereference_ a null. But no one is >>> deferencing anything here; it's just that Box(null) fell into that middle >>> space >>> of "well, you didn't really cover it, but it's such a silly case that I >>> didn't >>> want to make you cover it either, but here we are and we have to do >>> something." >>> So maybe want some sort of SillyCaseException (perhaps with a less silly >>> name) >>> for at least the null residue. >> I believe that if Head and Tail exhaustively cover an enum or sealed type (as >> was the intended implication of my example)—more generally, in a situation >> that >> is optimistically total---then the user would be very happy to have some sort >> of error signaled if some other value shows up unexpectedly in a statement >> switch, whether that value is “Ankle" or “null”. Maybe a new error name >> would >> be appropriate, such as UnexpectedNull. >> If the user does not want such implicit handling of an optimistically total >> situation in a statement switch, then it is always possible to provide >> explicit >> clauses “case null: break;” and “default: break;”. >>> On the other hand, ICCE for Box(novel) does seem reasonable because the >>> world >>> really has changed in an incompatible way since the user wrote the code, and >>> they probably do want to be alerted to the fact that their code is out of >>> sync >>> with the world. >> Yep. >>> Separately (but not really separately), I'd like to refine my claim that >>> `switch` is null-hostile. In reality, `switch` NPEs on null in three >>> cases: a >>> null enum, String, or primitive box. And, in each of these cases, it NPEs >>> because (the implementation) really does dereference the target! For a >>> `String`, it calls `hashCode()`. For an `enum`, it calls `ordinal()`. And >>> for >>> a box, it calls `xxxValue()`. It is _those_ methods that NPE, not the >>> switch. >>> (Yes, we could have designed it so that the implementation did a null check >>> before calling those things.)