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