[pattern-switch] Exhaustiveness
forax at univ-mlv.fr
forax at univ-mlv.fr
Sat Aug 22 12:01:28 UTC 2020
> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Guy Steele" <guy.steele at oracle.com>
> Cc: "Remi Forax" <forax at univ-mlv.fr>, "Tagir Valeev" <amaembo at gmail.com>,
> "amber-spec-experts" <amber-spec-experts at 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.goetz at oracle.com |
>>> <brian.goetz at 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.)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20200822/b7ba064c/attachment.htm>
More information about the amber-spec-experts
mailing list