>> I agree destructuring is just as important as conditionality and those two
>> things should be orthogonal.
>> But i still think having a keyword to signal that a pattern (not a case) is
>> total is better than letting people guess.
I’m glad this discussion has moved to the right thing — whether we should
provide extra help in type-checking totality. (Note, though, there were
several dozen aspects to my patterns-in-switch proposal, and we’ve spent 99% of
our time discussing a single, pretty cornery one. This is the hazard of
discussing these things on mailing lists, where all the discussion is replies
to the first thing that someone complained about, especially when the first
complaint is loud. So EVERYONE, PLEASE, take a detailed review through the
doc. It can’t be 100% perfect aside form this issue.)
I still think, though, that this may be mostly fear of the unknown. Having
spent the last year staring at pattern switches, it is really pretty obvious
which are the total ones. I think that will be true for most users, once they
use the feature a little. (Remember how alien people said method references
looked, or how worried people were about the fact that static mrefs and unbound
instance mrefs had the same syntax? Wasn’t a problem.) So all of this
discussion rests on the assumption that (a) it is really hard to tell when a
pattern is total or not, and (b) the stakes for getting that wrong are really
high. I worry that both of these concerns are significantly overstated, and
that the incremental user model complexity may be worse than the disease.
> Yes, and here is the example that convinced me that one needs to be able to
> mark patterns as total, not just cases:
The underlying observation here is that totality is a property of an arbitrary
sub-tree of a pattern, which could be the whole thing, a leaf, or some
intermediate sub-tree.
Guy’s exploration into overloading `default` does shine a light on one of the
uglier parts of my proposal, which is that the existing `default` case is
mostly left to rot. I’m going to take this as a cue that, whether we do
something to highlight pattern totality or not, that we should try to integrate
default cases better into the design.
So, here are the properties of the existing default case, in the current state
of the proposal:
- it matches all non-null values with no bindings
- it can only appear at the end (unlike the current language, where it can
appear anywhere)
- You cannot fall into it from a pattern case (unlike the current language,
where you can fall into it)
- People will either use default or a total pattern, but rarely both (since
their only difference is null)
Just as we’ve leaned towards rehabilitating switch, maybe we can try to
rehabilitate default. And the role it can play is in signaling totality of the
switch..
There are two “cases” of exhaustive switches: nullable ones and non-nullable
ones.
The nullable ones are those that end in a total (catch-all) pattern. The
non-nullable ones are the analogues of the current exhaustive switch on enums;
those where all the cases are “parts” of the whole:
switch (day) {
case MONDAY..FRIDAY: work();
case SATURDAY: play();
case SUNDAY: worship();
// And on the null day, there was a NullPointerException
}
This is a total switch, but not a nullable one. On the other hand:
switch (something) {
case X: ...
case Object o:
}
This is also a total switch, but a nullable one.
So here’s a proposed rehabilitation of default, inspired by Guy’s exploration:
- A switch has a sequence of cases, with zero or one default clases
- The default case must be last (except for legacy switches)
- A switch with a default case must be total (possibly modulo null)
- Default can be used with no pattern, which means “everything else but null”
- Default can be used with a pattern, in which case it has exactly the same
semantics as the same pattern with “case”
- Nullability is still strictly a function of whether there are any nullable
cases, which can only be “case null” (first) and total pattern (last)
So we can say
switch (something) {
case X: ...
case Object o:
}
or
switch (something) {
case X: ...
default Object o:
}
which mean the same thing, but the latter engages the additional type checking
that the switch is total.
This is not inconsistent with GUy’s sketch, it just is the switch-specific part.