Next up for patterns: type patterns in switch

Brian Goetz brian.goetz at oracle.com
Tue Aug 11 22:43:59 UTC 2020


Let's assume that your trick works fine, Remi is happy, and `default P` 
means that P is total, that the switch is total, everything.  Great.  
Now, what is the story for nested patterns?

    switch (container) {
        case Box(Frog f): ...
        case Box(Chocolate c): ...
        case Box(var x): ....

        case Bag(Frog f): ...
        case Bag(Chocolate c): ...
        case Bag(var x): ....

     }

We still have totality at the nested level; when container is a box or a 
bag, the catch-all case is total on that kind of container, but no one 
has said "default" or "total" or anything like that.  And a Box(null) 
will get dumped into the third case.  Do we care?

I don't, really, but you knew that.  Remi might, but we'll have to hear 
from him.  But I offer this example as a bisection to determine whether 
the discomfort is really about totality-therefore-nullable in patterns, 
or really just about exhaustive switches (and the nullity thing is a red 
herring.)

So, who is bothered by the fact that case #3 gets Box(null), and case #6 
gets Bag(null)?  Anyone?  (And, if not, but you are bothered by the lack 
of totality on the true catch-alls, why not?)

On 8/11/2020 6:37 PM, Guy Steele wrote:
>
>
>> On Aug 11, 2020, at 6:26 PM, Brian Goetz <brian.goetz at oracle.com 
>> <mailto:brian.goetz at oracle.com>> wrote:
>>
>>
>>> On the other hand, I think Remi’s point about totality being an 
>>> implicit and non-local property that is easily undermined by code 
>>> changes in another compilation unit is worrisome.
>>
>> ... which in turn derives from something else worrisome (a problem we 
>> bought last year): that it is not clear from looking at a switch 
>> whether it is exhaustive or not. Expression switches must exhaustive, 
>> but statement switches need not be.  Here, we are saying that 
>> exhaustive switch statements are a useful new thing (which they are) 
>> and get rewarded with new behaviors (some may not find it a reward), 
>> but you have to look too closely to determine whether the switch is 
>> total.  If it is, the last clause is total too (well, unless it is an 
>> enum switch that names all the constants, or a switch over a sealed 
>> type that names all the sub-types but without a catch-all.)
>>
>> So I claim that, if there is a problem, it is that it should be more 
>> obvious that a switch is exhaustive on its target.
>>
>>> Putting this all together, I reach two conclusions:
>>>
>>> (1) We can live with the current definition of instanceof, provided 
>>> we make it clear that instanceof is not purely equivalent to pattern 
>>> matching, and that instanceof and pattern matching can be simply 
>>> defined in terms of each other.
>>
>> I think we can do slightly better than this.  I argue that
>>
>>     x instanceof null
>>
>> is silly because we can just say
>>
>>     x == null
>>
>> instead (which is more direct), and similarly
>>
>>     x instanceof var y
>>     x instance of Object o
>>
>> are silly because we can just say
>>
>>     var y = x
>>
>> instead (again more direct).  So let's just ban the nullable patterns 
>> in instanceof, and no one will ever notice.
>>
>>> (2) We have a real disagreement about switch, but I think the fault 
>>> lies in the design of switch rather than with pattern matching, and 
>>> the fault is this:
>>>
>>> Sometimes when we write
>>>
>>> switch (v) {
>>> case Type1 x: A
>>> case Type2 y: B
>>> case Type3 z: C
>>> }
>>>
>>> we mean for Type1 and Type 2 and Type3 to be three disparate and 
>>> co-equal things—in which case it seems absurd for any of them to 
>>> match null; but other times we mean for Type3 to be a catchall, in 
>>> which case we do want it to match null if nothing before it has.
>>
>> Agreed.  The fundamental concern that Remi (and Stephen, over on 
>> a-dev) have raised is that we can't tell which it is, and that is 
>> disturbing.  (I still think it won't matter in reality, but I 
>> understand the concern.)  The same ambiguity happens with 
>> deconstruction patterns:
>>
>>     case Type3(var x, var y, var z) t3: ...
>>
>> which we can think of as "enhanced" type patterns.
>
> Sure.
>
>> (encouraging that our mails crossed with mostly the same observation 
>> and possible fix.)
>>
>>> I believe some previous discussion has focused on ways to modify the 
>>> _pattern_ to indicated either an expectation of totality or a 
>>> specific way of handling null.  But at this point I think augmenting 
>>> patterns is overkill; what we need (and all we need) is a 
>>> modification to the syntax of switch to indicate an expectation of 
>>> totality.  I have a modest suggestion:
>>>
>>> switch (v) {
>>> case Type1 x: A
>>> case Type2 y: B
>>> case Type3 z: C    // Type3 is not expected to be a catchall
>>> }
>>>
>>> switch (v) {
>>> case Type1 x: A
>>> case Type2 y: B
>>> default case Type3 z: C    // Type3 is expected to be a catchall; it 
>>> is a static error if Type3 is not total on v,
>>> // and Type3 will match null (unlike Type1 and Type2)
>>> }
>>
>> And we already had another reason to want something like this: 
>> expression switches are exhaustive, statement switches are not, and 
>> we'd like to be able to engage the compiler to do exhaustiveness 
>> checking for statement switches even in the absence of patterns.
>>
>>> Now, I will admit that this syntax is a wee bit delicate, because 
>>> adding a colon might apparently change the meaning:
>>>
>>> switch (v) {
>>> case Type1 x: A
>>> case Type2 y: B
>>> default: case Type3 z: C
>>> }
>>
>> Or `final case` or `finally <pattern>` or `default-case` or ...
>>
>> I am iffy about `default` because of its historical association, but 
>> I will have to re-think it in light of this idea before I have an 
>> opinion.
>
> I don;t care about the syntax very much.  I thought of “default” 
> because it sort of communicates the right idea and is already a 
> keyword: it says that the last clause is BOTH a case (with a pattern) 
> but also a catchall.
>
> I have to admit that “default case” (there is really no need for a 
> hyphen here) is a bit wordy compared to “finally”, which is very 
> clever but could cause some cognitive dissonance in users who think 
> too hard about “try” (really? a case clause that is always executed 
> before you exit the switch??).
>
>>> but I believe that in situations that matter, the compiler can and 
>>> will reject this last example on other grounds (please correct me if 
>>> I am mistaken
>>
>> yes, the compiler can catch this.
>>
>> The other degree of freedom on this mini-feature is whether `default` 
>> is a hint, or whether it would be an error to not say `default` on a 
>> total pattern.  I think it might be seen as a burden if it were 
>> required, but Remi might think it not strong enough if its just a hint.
>
> Yeah, I thought about that, and decided that it would be a bad idea 
> for the compiler to complain about the absence of “default”, in part 
> because you don't want to feel vaguely obligated to include it in 
> simple cases involving, for example, exhaustive use of enums.
>
>
>



More information about the amber-spec-observers mailing list