[External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)
Stephen Colebourne
scolebourne at joda.org
Wed Jan 26 15:57:45 UTC 2022
Could I try again to explain why `Type t` is currently painful in
switch, and propose what I believe is a new alternative approach?
A year ago, Brian wrote "no one disagrees that `String s` should match
all non-null instances of String". But the current proposal does in
fact disagree. The reality is that `String s` can match null - it
depends on what static context of the target is.
My primary concern is that type patterns need to be consistent with
*each other*. What I'm asking for is simply that a `Type t` pattern
always means *exactly* the same thing. Right now, it does not -
because the last pattern in a series of `Type t` patterns may or may
not be total and you often can't tell by reading the code:
switch (item) {
case Integer i ...
case Number n ...
}
Perhaps one way of helping understanding is that `Type t` is
essentially an ambiguous pattern with two possible meanings - either
match dynamically as per instanceof, or match against the static
context of the target (and thus be total and accept null).
Normally the Java compiler rejects ambiguous code (or the language
designer arranges for the ambiguity not to occur). But the ambiguous
code is not rejected here. IMO that is the root cause of the issue
here, which remains unsolved. Proposals that tweak around the edges in
switch/instanceof are not tackling that root cause.
A (silly) verbose solution to this would be to add more keywords to
distinguish the ambiguity, eg:
switch (item) {
case instanceof Number n ...
case instanceof String s ...
case instanceof Box(instanceof String bs) ...
case instanceof Box(totalmatch Object bt) ...
case instanceof Object o ...
case totalmatch Object t ...
}
Clearly such a verbose approach won't be popular, so here is an
alternative I've not seen discussed before:
Looking at local variable declarations, imagine `var t` and `Type t`
are both shorthand for a new local variable declaration form `var Type
t`. This would be immediately beneficial for local variables, eg.
var Predicate<String> p = str -> str.length() < 2
var LocalDate date = null;
(both of which require a cast today if you want to use var consistently)
Now we can use this new verbose declaration form to resolve the
ambiguity in pattern matching:
- `Type t` - check dynamically as per instanceof, cannot be used for
total matching
- `var t` - use the static context and insist on a total match
- `var Type t` - use the static context and insist on a total match using Type
As a pattern and as a local variable declaration, `var t` can always
be replaced with `var Type t`.
As a pattern, `Type t` rejects null, but as a local variable
declaration it allows null.
When using a local variable pattern match to destructure a method
return it has to be total, thus it has to use `var t` or `var Type t`
(not `Type t`)
Sure, it is no longer true that `var obj` and `Object obj` patterns
mean exactly the same thing, but it can now be explained as part of a
larger story wrt `var Object obj`. That is the novel part of this
idea.
The verbose example above would therefore be one of these two options,
depending on your taste/needs:
switch (item) {
case Number n ...
case String s ...
case Box(String bs) ...
case Box(var bt) ...
case Object o ...
case var t ...
}
switch (item) {
case Number n ...
case String s ...
case Box(String bs) ...
case Box(var Object bt) ...
case Object o ...
case var Object t ...
}
IMO, most people would use the former (Remi's C# approach, and my
preferred approach, which is nice and clear). But if you want to add
the actual type you can - you just have to keep the var to demonstrate
totality.
Anyway, thanks for listening.
Stephen
More information about the amber-spec-comments
mailing list