> De: "Brian Goetz" <[email protected]>
> À: "Guy Steele" <[email protected]>
> Cc: "Remi Forax" <[email protected]>, "Tagir Valeev" <[email protected]>,
> "amber-spec-experts" <[email protected]>
> 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:[email protected] |
>>> <[email protected]> ] 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.)