[pattern-switch] Totality

Brian Goetz brian.goetz at oracle.com
Fri Aug 21 20:18:37 UTC 2020



On 8/21/2020 11:14 AM, Brian Goetz wrote:
>
> Next up (separate topic): letting statement switches opt into totality.
>

Assuming the discussion on Exhaustiveness is good, let's talk about 
totality.

Expression switches must be total; we totalize them by throwing when we 
encounter any residue, even though we only require that the set of cases 
in the switch be optimistically total.  Residue includes:

  - `null` switch targets in String, Enum, and primitive box switches only;
  - novel values in enum switches without a total case clause;
  - novel subtypes in switches on sealed types without a total case clause;
  - when an optimistically total subchain of deconstruction pattern 
cases wraps a residue value (e.g., D(null) or D(novel))

What about statement switches?  Right now, any residue for a statement 
switch without a total case clause will just be silently ignored 
(because statement switches need not be total.)

What we would like is a way to say "this switch is total, please type 
check it for me as such, and insert any needed residue-catching cases."  
I think this is a job for `default`.

Now that we've got some clarity that switches _don't_ throw on null, but 
instead it is as if string/enum/box switches have an implicit `case 
null` when no explicit one is present, we can define `default`, once 
again, to be total (and not just weakly total.)  So in:

     switch (object) {
         case "foo":
         case Box(Frog fs):
         default: ...
     }

a `null` just falls into `default` just like anything else that is not 
the string "foo" or a box of frogs ("let the nulls flow"). Default would 
have to come last (except in legacy switches, where a legacy switch has 
one of the distinguished target types and all constant case labels.)

What if we want to destructure too?  Well, add a pattern:

     switch (object) {
         case "foo":
         case Box(Frog fs):
         default Object o: ...
     }

This would additionally assert that the following pattern is total, 
otherwise a compilation error ensues.  (Note, though, that this is 
entirely about `switch`, not patterns.  The semantics of the pattern is 
unchanged, and I do not believe that sprinkling `default` into nested 
patterns to shout "TOTALITY HERE, I MEAN IT" carries its weight.)

This seems a better job to give default in this new world; anything not 
previously matched, where we retcon the current null behavior as being 
only about string, enum, or boxes.

This leaves us with only one hole, which is: suppose I have an 
_optimistically total_ statement switch.   Users might like to (a) 
assert the switch is total, and get the concomitant type checking, and 
(b) get residue ejection for free.  Of the two, though, A is much more 
important than B, but we'll take B when we can get it. Perhaps, if the 
target of a switch is a sealed type, we can interpret:

     switch (shape) {
         case Rect r: ...
         default Circle c: ...
     }

as meaning that `Circle c` _closes_ the switch to make it total, and 
engages the totality checking to ensure this is true.  So, `default P` 
would mean either:

  - P is total, or
  - P is not total, but taken with the other cases, makes the switch 
optimistically total

and in the latter case, would engage the residue-detection-and-ejection 
machinery.

This might be stretching it a tad too far, but I like that we can given 
`default` useful new jobs to do in `switch` rather than just giving him 
a gold watch.




More information about the amber-spec-observers mailing list