Next up for patterns: type patterns in switch
forax at univ-mlv.fr
forax at univ-mlv.fr
Wed Aug 12 13:45:30 UTC 2020
> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Guy Steele" <guy.steele at oracle.com>
> Cc: "Remi Forax" <forax at univ-mlv.fr>, "John Rose" <john.r.rose at oracle.com>,
> "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Envoyé: Mercredi 12 Août 2020 00:43:59
> Objet: Re: Next up for patterns: type patterns in switch
> Let's assume that your trick works fine, Remi is happy, and `default P` means
> that P is total, that the switch is total, everything. Great.
yep, i'm hapy with "default P", let me explain why :
I see the switch semantics as a kind of compact way to represent a cascade of if-else,
with that they are several pattern
case Constant is equivalent to if (o.equals(Constant)) or if (o == Constant)
case null is equivalent to if (o == null)
case P p is equivalent of if (o instanceof P p)
and
default P p is equivalent to else { P p = o; }
some patterns accept null (case null and default P), so if one of them is present in a switch, the switch accept null, otherwise it does not.
so the following code
if (o instanceof Frog f) { ... }
else if (o instanceof Chocolate c) { ... }
else { var x = o; ... }
can be refactored to
switch(o) {
case Frog f: ...
case Chocolate c: ...
default var x: ...
}
About exhaustiveness, if a switch is exhaustive, by example with
sealed interface Container permits Box, Bag { }
the following switch is exhaustive
switch(container) {
case Box box: ...
case Bag bag: ...
}
Here there is no need for a total pattern and if a user want to allow null, he can add a "case null".
> Now, what is the story for nested patterns?
> switch (container) {
> case Box(Frog f): ...
> case Box(Chocolate c): ...
> case Box(var x): ....
> case Bag(Frog f): ...
> case Bag(Chocolate c): ...
> case Bag(var x): ....
> }
so this is a mix between an exhaustive switch with two total patterns once de-constructed, for me, it should be written like this
switch(container) {
case Box(Frog g): ...
case Box(Chocolate c): ...
default Box(var x): ...
case Bag(Frog g): ...
case Bag(Chocolate c): ...
default Bag(var x): ...
}
using the syntax "default Box(var x)" to say that the nested-patterns are locally total thus accept null.
It's a little weird to have the "default" in front of the type name while it applies on the nested part but i'm Ok with that.
> We still have totality at the nested level; when container is a box or a bag,
> the catch-all case is total on that kind of container, but no one has said
> "default" or "total" or anything like that. And a Box(null) will get dumped
> into the third case. Do we care?
> I don't, really, but you knew that. Remi might, but we'll have to hear from him.
> But I offer this example as a bisection to determine whether the discomfort is
> really about totality-therefore-nullable in patterns, or really just about
> exhaustive switches (and the nullity thing is a red herring.)
I think it's a mix, at top-level it's an exhaustive switch so not nullable but if Box and the Bag may contains null, "Box(var x)" and "Bag(var x)" should use a default pattern because there a kind of locally total.
> So, who is bothered by the fact that case #3 gets Box(null), and case #6 gets
> Bag(null)? Anyone? (And, if not, but you are bothered by the lack of totality
> on the true catch-alls, why not?)
I'm bothered if the pattern are not declared as total and i believe Stephen Colebourne on amber-dev is proposing exactly the same rules.
Rémi
> On 8/11/2020 6:37 PM, Guy Steele wrote:
>>> On Aug 11, 2020, at 6:26 PM, Brian Goetz < [ mailto:brian.goetz at oracle.com |
>>> brian.goetz at oracle.com ] > wrote:
>>>> On the other hand, I think Remi’s point about totality being an implicit and
>>>> non-local property that is easily undermined by code changes in another
>>>> compilation unit is worrisome.
>>> ... which in turn derives from something else worrisome (a problem we bought
>>> last year): that it is not clear from looking at a switch whether it is
>>> exhaustive or not. Expression switches must exhaustive, but statement switches
>>> need not be. Here, we are saying that exhaustive switch statements are a useful
>>> new thing (which they are) and get rewarded with new behaviors (some may not
>>> find it a reward), but you have to look too closely to determine whether the
>>> switch is total. If it is, the last clause is total too (well, unless it is an
>>> enum switch that names all the constants, or a switch over a sealed type that
>>> names all the sub-types but without a catch-all.)
>>> So I claim that, if there is a problem, it is that it should be more obvious
>>> that a switch is exhaustive on its target.
>>>> Putting this all together, I reach two conclusions:
>>>> (1) We can live with the current definition of instanceof, provided we make it
>>>> clear that instanceof is not purely equivalent to pattern matching, and that
>>>> instanceof and pattern matching can be simply defined in terms of each other.
>>> I think we can do slightly better than this. I argue that
>>> x instanceof null
>>> is silly because we can just say
>>> x == null
>>> instead (which is more direct), and similarly
>>> x instanceof var y
>>> x instance of Object o
>>> are silly because we can just say
>>> var y = x
>>> instead (again more direct). So let's just ban the nullable patterns in
>>> instanceof, and no one will ever notice.
>>>> (2) We have a real disagreement about switch, but I think the fault lies in the
>>>> design of switch rather than with pattern matching, and the fault is this:
>>>> Sometimes when we write
>>>> switch (v) {
>>>> case Type1 x: A
>>>> case Type2 y: B
>>>> case Type3 z: C
>>>> }
>>>> we mean for Type1 and Type 2 and Type3 to be three disparate and co-equal
>>>> things—in which case it seems absurd for any of them to match null; but other
>>>> times we mean for Type3 to be a catchall, in which case we do want it to match
>>>> null if nothing before it has.
>>> Agreed. The fundamental concern that Remi (and Stephen, over on a-dev) have
>>> raised is that we can't tell which it is, and that is disturbing. (I still
>>> think it won't matter in reality, but I understand the concern.) The same
>>> ambiguity happens with deconstruction patterns:
>>> case Type3(var x, var y, var z) t3: ...
>>> which we can think of as "enhanced" type patterns.
>> Sure.
>>> (encouraging that our mails crossed with mostly the same observation and
>>> possible fix.)
>>>> I believe some previous discussion has focused on ways to modify the _pattern_
>>>> to indicated either an expectation of totality or a specific way of handling
>>>> null. But at this point I think augmenting patterns is overkill; what we need
>>>> (and all we need) is a modification to the syntax of switch to indicate an
>>>> expectation of totality. I have a modest suggestion:
>>>> switch (v) {
>>>> case Type1 x: A
>>>> case Type2 y: B
>>>> case Type3 z: C // Type3 is not expected to be a catchall
>>>> }
>>>> switch (v) {
>>>> case Type1 x: A
>>>> case Type2 y: B
>>>> default case Type3 z: C // Type3 is expected to be a catchall; it is a static
>>>> error if Type3 is not total on v,
>>>> // and Type3 will match null (unlike Type1 and Type2)
>>>> }
>>> And we already had another reason to want something like this: expression
>>> switches are exhaustive, statement switches are not, and we'd like to be able
>>> to engage the compiler to do exhaustiveness checking for statement switches
>>> even in the absence of patterns.
>>>> Now, I will admit that this syntax is a wee bit delicate, because adding a colon
>>>> might apparently change the meaning:
>>>> switch (v) {
>>>> case Type1 x: A
>>>> case Type2 y: B
>>>> default: case Type3 z: C
>>>> }
>>> Or `final case` or `finally <pattern>` or `default-case` or ...
>>> I am iffy about `default` because of its historical association, but I will have
>>> to re-think it in light of this idea before I have an opinion.
>> I don;t care about the syntax very much. I thought of “default” because it sort
>> of communicates the right idea and is already a keyword: it says that the last
>> clause is BOTH a case (with a pattern) but also a catchall.
>> I have to admit that “default case” (there is really no need for a hyphen here)
>> is a bit wordy compared to “finally”, which is very clever but could cause some
>> cognitive dissonance in users who think too hard about “try” (really? a case
>> clause that is always executed before you exit the switch??).
>>>> but I believe that in situations that matter, the compiler can and will reject
>>>> this last example on other grounds (please correct me if I am mistaken
>>> yes, the compiler can catch this.
>>> The other degree of freedom on this mini-feature is whether `default` is a hint,
>>> or whether it would be an error to not say `default` on a total pattern. I
>>> think it might be seen as a burden if it were required, but Remi might think it
>>> not strong enough if its just a hint.
>> Yeah, I thought about that, and decided that it would be a bad idea for the
>> compiler to complain about the absence of “default”, in part because you don't
>> want to feel vaguely obligated to include it in simple cases involving, for
>> example, exhaustive use of enums.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20200812/663fd82e/attachment-0001.htm>
More information about the amber-spec-experts
mailing list