It's a good time to check in on this list, because I think we've made a
lot of progress in the last weak clearing away the layers of overgrowth
that have gotten in the way of seeing a clear story. Here's my summary
of what's been cleared away. (Reminder: this is the summary thread, so
its for summary information only, if you want to argue with the specific
points I'm making, take it to the right thread.)
Totality and patterns. I think it's even more obvious at this point
that the only realistic interpretation of `T t` as a pattern is that it
is total on `T`, including null. Anything else adds value-destroying
complexity when we try to compose patterns. But, it was hard to see
this because ...
Nullity and switch. We came at this with the mistaken assumption that
"switches are just null-hostile." But that was wrong. After digging at
it, we realized that the null-hostility of switch was an artifact of the
limited domains to which switch had been originally applied. When we
zoom out, it becomes obvious that the null-hostility of switch is not
scalable (we even tried to distort the semantics of patterns to try to
accomodate it.) The obvious move here is:
- Allow `case null` in all reference switches, with the obvious semantics;
- Enums, strings, and boxes get an implicit `case null: throw` at the
top if there is no explicit `case null`;
- Total patterns -- including default -- match null
So this means that _all_ switches are nullable, but these special target
types bring their own special null defaults. While we first thought it
might be the switch that was throwing, and then we thought maybe it was
the (possibly implicit) default that was throwing, in reality, it is the
invisible `case null` that comes with switching on these special types.
(Meta: this shows how much damage the "blow early, blow often" rule does
-- by introducing ad-hoc rules to try to create a null-free playing
field, trying to undo even one of these can take weeks of analysis to
unravel.)
These two moves clear away almost all of the null hazards with switch,
and put us on a principled foundation: with the exception of the legacy
switch types, null is just another value that flows through patterns and
switches, which can be matched, with the obvious and now-composible
semantics. I think this also should reduce the "totality is too subtle"
concern, because we've put the null hostility into a more well-defined
"box" -- switches are just nullable, so *of course* the nulls flow into
the total pattern.
Separately, there are TWO issues regarding switch totality. The first
is how we can give statement switches the same error checking for
totality that statement switches currently enjoy. I think there is npw
room to cleanly repurpose `default` for this.
The second is, while tangentially related to nullity, is about
_optimistic totality_. The optimistic totality we embraced in
expression switches over enums does not scale quite cleanly yet to
sealed types, and specifically to _lifting_ type patterns over sealed
types. We have more work to do here.
(One of the underappreciated moves of the optimistic totality we did in
12 is that it is _better_ to leave out the default clause when a switch
is believed to be optimistically total, because it leads to better type
checking -- omissions and separate compilation artifacts are detected at
compile time rather than runtime. We would like to get the same for
sealing.)
So, summary:
- Type patterns `T t` are total on U <: T, and `var t` is total on all
types;
- Total patterns match null;
- switches are not null hostile;
- `default` is a total switch case;
- you can say `case null` in switch;
- For switches on *enums, strings, and boxes*, there is an implicit
`case null` that throws, but you can override this with an explicit
`case null`. (There's still some fine points to discuss here; if we
want `case null` to be able to fall into default, then we can't require
it be at the top.)
- We can consider enhancing `default` to take a total destructuring
pattern with minimal distortion;
- We need to have a longer conversation about optimistic totality.
I think that's good progress for the week, as it checks off 3 of the
items on this list.
On 8/14/2020 1:19 PM, Brian Goetz wrote:
Here's a summary of the issues raised in the reviews of the
patterns-in-switch document. I'm going to (try to) start a new thread
for each of them; let's not reply to this one with new topics (or with
discussion on these topics.) I'll update this thread as we add or
remove things from the list.
- Is totality too subtle? (Remi) There is some concern that the
notion of using totality to subsume nullability (at least in nested
contexts) is sound, he is concerned that the difference between total
and non-total patterns may be too subtle, and this may lead to NPE
issues. To evaluate this, we need to evaluate both the "is totality
too subtle" and the "how much are we worried about NPE in this
context" directions.
- Guards. (John, Tagir) There is acknowledgement that some sort of
"whoops, not this case" support is needed in order to maintain switch
as a useful construct in the face of richer case labels, but some
disagreement about whether an imperative statement (e.g., continue) or
a declarative guard (e.g., `when <predicate>`) is the right choice.
- Exhaustiveness and null. (Tagir) For sealed domains (enums and
sealed types), we kind of cheated with expression switches because we
could count on the switch filtering out the null. But Tagir raises an
excellent point, which is that we do not yet have a sound definition
of exhaustiveness that scales to nested patterns (do Box(Rect) and
Box(Circle) cover Box(Shape)?) This is an interaction between sealed
types and patterns that needs to be ironed out. (Thanks Tagir!)
- Switch and null. (Tagir, Kevin) Should we reconsider trying to
rehabilitate switches null-acceptance? There are several who are
questioning whether this is trying to push things too far for too
little benefit.
- Rehabilitating default. The current design leaves default to rot;
it is possible it has a better role to play with respect to the
rehabilitation of switch, such as signalling that the switch is total.
- Restrictions on instanceof. It has been proposed that we restrict
total patterns from instanceof to avoid confusion; while no one has
really objected, a few people have expressed mild discomfort. Leaving
it on the list for now until we resolve some of the other nullity
questions.
- Meta. (Brian) Nearly all of this is about null. Is it possible
that everything else about the proposal is so perfect that there's
nothing else to talk about? Seems unlikely. I recommend we turn up
the attenuation knob on nullity issues to leave some oxygen for some
of the other flowers.