Switch labels (null again), some tweaking
Brian Goetz
brian.goetz at oracle.com
Wed Apr 28 20:11:49 UTC 2021
Armed with this observation, here's a restack of the terminology and
rules, but with no semantic differences from the current story, just new
names.
The basic idea is:
total on T with no remainder -> total on T
total on T with remainder R -> covers T, excepting/modulo R
Concepts:
- A pattern P can be _total_ on a type T. Total patterns match every
element of the value set of T, including null.
- A set of patterns P* can _cover_ a type T, excepting/modulo R*.
This means that for every element in the value set of T that is not
matched by some pattern in R*, it is matched by some pattern in P*.
- If P is total on T, { P } covers T (with no exceptions.)
Base cases:
- (type patterns) The type pattern `T t` is total on all U <: T
- (var patterns) The var pattern `var x` is total on all T
- (default) The label `default` corresponds to a pattern that covers
all types, modulo null.
- (sealing) For an abstract sealed type S permitting T0..Tn, the set
of patterns { T0, T1, ... Tn } covers S, modulo null and novel subtypes
of S.
Induction cases:
- (lifting) For a deconstruction pattern D(T), { D(Q) : Q in Q* }
covers D (modulo D(R*) and null) iff Q* covers T modulo R.
Construct restrictions:
- The pattern on the RHS of instanceof may not be total on the type of
the LHS.
- In a (non-legacy) switch on x : T, the set of patterns in the case
labels must cover T, excepting some remainder. It throws on the remainder.
- In a pattern assignment `P = e`, where `e : T`, P must be cover T.
It throws on the remainder.
This is just a restack of the terms, but I think it eliminates the most
problematic part, which is 'total with remainder'. Now, total means
total. Total with remainder becomes "covers, excepting".
On 4/28/2021 2:13 PM, Brian Goetz wrote:
> I think you're right that the terminology is currently biased for
> mathematicians, which is a necessary initial phase to prove that the
> language is right, but needs to get to the part where the terminology
> makes sense to Joe Java.
>
> The notion of "total with remainder" is indeed confusing, and we need
> to find a better way to say it, but we come by it honestly. Because
> the "remainder" corresponds to cases where no reasonable Java
> developer would want to be forced to write them out, such as "novel
> enum value".
>
> I think we can make things slightly (but not enough) better by using
> "total" for single patterns only, and using "exhaustive" for sets of
> patterns, since that's what drives switch exhaustiveness.
>
> But that's only a start.
>
> On 4/28/2021 2:09 PM, Maurizio Cimadamore wrote:
>>
>> I think I got the two main fallacies which led me down the wrong path:
>>
>> 1. there is a distinction between patterns that are total on T, and
>> patterns that are total on T, but with a "remainder"
>>
>> 2. there is a distinction between a pattern being total on T, and a
>> set of patterns being total (or exhaustive) on T
>>
>> This sent me completely haywire, because I was trying to reason in
>> terms of what a plain pattern instanceof would consider "total", and
>> then translate the results to nested patterns in switch - and that
>> didn't work, because two different sets of rules apply there.
>>
>> Maurizio
>>
>> On 28/04/2021 18:27, Brian Goetz wrote:
>>> I think part of the problem is that we're using the word "total" in
>>> different ways.
>>>
>>> A pattern P may be total on type T with remainder R. For example,
>>> the pattern `Soup s` is total on `Soup` with no remainder.
>>>
>>> A _set_ of patterns may be total on T with remainder R as well.
>>> (The only way a set of patterns is total is either (a) one of the
>>> patterns in the set is already total on T, OR (b) sealing comes into
>>> play.) Maybe this should be called "exhaustive" to separate from
>>> pattern totality.
>>>
>>> Switch exhaustiveness derives from set-totality.
>>>
>>> Instanceof prohibits patterns that are total without remainder, for
>>> two reasons: (1) its silly to ask a question which constant-folds to
>>> `true`, and (b) the disagreement between traditional `instanceof
>>> Object` and `instanceof <total pattern>` at null would likely be a
>>> source of bugs. (This was the cost of reusing instanceof rather
>>> than creating a new "matches" operator.)
>>>
>>>> Foo x = ...
>>>> if (x instanceof Bar)
>>>>
>>>> The instanceof will not be considered total, and therefore be
>>>> accepted by the compiler (sorry to repeat the same question - I
>>>> want to make sure I understand how totality works with sealed
>>>> hierarchies).
>>>>
>>>
>>> If the RHS of an `instanceof` is a type (not a type pattern), then
>>> this has traditional `instanceof` behavior. If Bar <: Foo, then
>>> this is in effect a null check.
>>>
>>> If the RHS is a _pattern_, then the pattern must not be total
>>> without remainder. If Bar <: Foo, `Bar b` is total on Foo, so the
>>> compiler says "dumb question, ask a different one."
>>>
>>> If the RHS is a non-total pattern, or a total pattern with
>>> remainder, then there's a real question being asked. So in your
>>> Lunch-permits-Soup example, you could say
>>>
>>> if (lunch instanceof Soup s)
>>>
>>> and this matches _on non-null lunch_. Just like the switch. The
>>> only difference is switch will throw on unmatched nulls, whereas
>>> instanceof says "no, not an instance", but that's got nothing to do
>>> with patterns, it's about conditional constructs.
>>>
>>>> If that's the case, I find that a bit odd - because enums kind of
>>>> have the same issue (but we have opted to trust that a switch on an
>>>> enum is total if all the constants known at compile time are
>>>> covered) - and, to my eyes, if you squint, a sealed hierarchy is
>>>> like an enum for types (e.g. sum type).
>>>>
>>>
>>> OK, let's talk about enums. Suppose I have:
>>>
>>> enum Lunch { Soup }
>>>
>>> and I do
>>>
>>> switch (lunch) {
>>> case Soup -> ...
>>> }
>>>
>>> What happens on null? It throws, and it always has. The behavior
>>> for the sealed analogue is the same; the `Soup s` pattern matches
>>> the non-null lunches, and if null is left unhandled elsewhere in the
>>> switch, the switch throws. No asymmetry.
>>>
>>>> Anyway, backing up - this below:
>>>>
>>>> ```
>>>>
>>>> switch (lunch) {
>>>> case Box(Soup s):
>>>> System.err.println("Box of soup");
>>>> break;
>>>>
>>>> case Bag(Soup s):
>>>> System.err.println("Bag of soup");
>>>> break;
>>>>
>>>> /* implicit */
>>>> case Box(null), Bag(null): throw new NPE();
>>>> }
>>>> ```
>>>>
>>>> is good code, which says what it means. I think the challenge will
>>>> be to present error messages (e.g. if the user forgot to add case
>>>> Box(null)) in a way that makes it clear to the user as to what's
>>>> missing; and maybe that will be enough.
>>>>
>>>
>>> The challenge here is that we don't want to force the user to handle
>>> the "silly" cases, such as:
>>>
>>> Boolean bool = ...
>>> switch (bool) {
>>> case true -> ...
>>> case false -> ...
>>> case null -> ... // who would be happy about having to
>>> write this case?
>>> }
>>>
>>> and the similarly lifted:
>>>
>>> Box<Boolean> box = ...
>>> switch (box) {
>>> case Box(true) -> ...
>>> case Box(false) -> ...
>>> case Box(null) -> ... // who would be happy about having to
>>> write this case?
>>> case Box(novel) -> ... // or this case?
>>> case null -> // or this case?
>>> }
>>>
>>> So we define the "remainder" as the values that "fall into the
>>> cracks between the patterns." Users can write patterns for these,
>>> and they'll match, but if not, the compiler inserts code to catch
>>> these and throw something.
>>>
>>> The benefit is twofold: not only does the user not have to write the
>>> stupid cases (imagine if Box had ten slots, would we want to write
>>> the 2^10 partial null cases?), but because we throw on the
>>> remainder, DA can treat the switch as covering all boxes, and be
>>> assured there are no leaks.
>>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20210428/1fb4d760/attachment-0001.htm>
More information about the amber-spec-experts
mailing list