I have thought about what you have just said; (a) I agree, and (b) have made me 
realize that I have unnecessarily and unreasonably conflated the o.t. issue 
with the fallthrough issue.  So let me try once more.  For context, here are 
the three important paragraphs again, and the only changes I have made are to 
remove from the third paragraph the sentences that address fallthrough and 
fallout:

While we're doing a conflation inventory, here's another candidate (which I will soon reject after drawing the distinction): totality checking vs automatic residue handling.  The interesting cases here all involve getting the compiler to do TWO categories of extra work for the user that is not done on old-school (partial, statement) switches:

 - Validating that the cases are (optimistically) total, or giving a compile error
 - Inserting synthetic cases to throw on the residue of optimistic totality

I'm not seriously suggesting that we should treat them as separate features, as much as observing that we may still only be circling the target.  In thinking about it this morning, I initially thought that a better axis for dividing the problem would be on the "shape" of the switch; some switches are "tree shaped" (type-hierarchy-based switches which lean hard on partial ordering of cases, usually culminating in catch-alls) and some are "star shaped" (all of the traditional switch examples, plus many switches over sealed types.)   I initially thought that `switch case` was mostly about the latter shape, but then I realized this is a false distinction; both produce residue, even when no sealing or enums are involved, as a side-effect of the fact that we want to not force users to spell out all the silly cases.

The pervasiveness of o.t., and the complex shape of residue that comes out of even well-behaved switches, says to me the place worth spending complexity budget on is not totality, but _optimistic totality with residue_.  (Strong totality is just residue = empty; weak totality is just residue = { null }, so collapsing these all may make sense.)  Because, the set of switches that are truly total with no optimism and no residue is pretty simple, and probably doesn't need an awful lot of help.  (For comparison, we don't need the language to help us assert than an if ... else chain is total; if the last entry is an "else", we're there.)  So I'm happy collapsing these two sub-features, but I want to keep the focus on the optimistic/residue part, because that's where the value is.

We already have a 2x2 matrix of orthogonal choices that we started with: { statement, expression } x { arrow, colon }, and we're not going to pare back that space.  So let's look at the other dimension, which you've currently got as { "single point of totality", optimistic totality, partiality }.

Given the lack of clear boundary between the tree-shaped and star-shaped switches with respect to their residue, I'm not sure the distinction between SPoT and optimistic totality carries its weight, and we can collapse these into "X-total vs partial", where X-total means both totality checking and automatic residue rejection.  The interesting part is validating that the programmer has contributed enough totality that the compiler can fill in the gaps -- let's highlight that.

To pick a different syntax for purposes of exposition, suppose we had `total-switch` variant of `switch`.  A `total-switch` would require that (a) the cases be at least optimistically total and (b) would insert additional throwing cases to patch any unhandled residue.  And we could do this with any of the combinations that lead to X-totality -- a default clause (strongly total), a weakly total deconstruction pattern (residue = null), an optimistically total set of type patterns for sealed types (residue = { null, novel }), and all the more complicated ones -- distinguishing between these cases doesn't seem interesting.  In this model, `default` just becomes a shorthand for "everything else, maybe with destructuring."

Which leaves:

   { statement, expression } x { arrow, colon } x { switch, total-switch }

where an expression switch of any stripe is automatically upgraded to a total-switch of that type.  I think this is still orthogonal (with the above caveat) since the fallthrough rules apply uniformly, and as mentioned above we can probably rehabilitate `default` so it can remain relevant in all the boxes.  (There are still some details to work out here, notably what to do about weakly total deconstruction patterns.)

The asymmetry where expression switches default to total, but statement switches default to partial is unfortunate, but we knew all along that was the necessary cost of rehabilitating switch rather than creating a parallel "snitch" (New Switch) construct. It's just the final payment is coming due now.

I would "strongly" prefer to not propagate `default` into patterns, but leave it as a behavior of switch.  I think our refined taxonomy of totality is now good enough to get us where we need to go without festooning pattern nesting with nullity knobs.  (I think that, if we have to include totality markers at every stage of the pattern syntax, we will have made a mistake somewhere else; as I mentioned to Remi when he brought up "var vs any" (which is just another spelling for default vs nothing), my objection is not to the syntax but to the amount of complexity budget it burns for a low-value thing -- raising ideally-ignorable corner cases into the user's direct field of view, when ideally we can define the natural positive space and let the rest fall into automatically handled residue.  If we have defined the pattern semantics correctly, I'm not sure anyone will notice.)

Which (if you buy all the above) leaves us with a bikeshed to paint on how to spell `total-switch` or `switch-case` or ...  ?


Reply via email to