Next up for patterns: type patterns in switch

forax at univ-mlv.fr forax at univ-mlv.fr
Tue Aug 11 15:42:56 UTC 2020


----- Mail original -----
> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "Remi Forax" <forax at univ-mlv.fr>, "John Rose" <john.r.rose at oracle.com>
> Cc: "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Envoyé: Mardi 11 Août 2020 15:57:36
> Objet: Re: Next up for patterns: type patterns in switch

>> - i don't think that framing the problem we have in term of null
>> haters / null friends is productive.
> 
> It may not be productive, but it is one of the elephants in the room.
> Some people sometimes take a hostile view of null ("We should prevent
> stream elements from being null!"), and, while this is often motivated
> by the best of intentions, it is a bias we need to be aware of.
> 

As far as i know, nobody in the EG has proposed that.

> 
>> There is a way to see a switch as a cascade of instanceof and to allow
>> null,
> 
> Herein lies danger, as both `switch` and `instanceof` have pre-existing,
> somewhat accidental null opinions.  But I agree that it is a good goal
> that switches and if-else chains of instanceof tests on the same target
> be refactorable to each other to the extent possible.
> 
>> This is almost the same semantics as the one you are proposing but
>> instead of the notion of totality, being the last case is enough to
>> accept null.
> 
> ... but what if the switch isn't total?  I can have
> 
>     switch (o) {
>         case Integer i: ...
>         case String s: ...
>         // no default, no total pattern
>     }
> 
> and it would be quite surprising to randomly shove nulls into the last case. 

It's not random, it's based on the fact that all cases but the last one are instanceof and the last one is a cast (see below)

> This would prevent the cases from being reordered even though
> they have no dominance ordering.  The reason the last case is special in
> the examples we've given so far is ... wait for it ... THEY ARE TOTAL.
> It would be a compiler error to have any more cases after them.  They
> are intrinsically catch-alls.

That why i have proposed first to syntactically make a difference using the syntax case String|null first but i'm sure someone can come with a better syntax.

Again, i understand why you think that a semantics based on the pattern being total or not make sense,
but you are only talking about the positive side and not the negative one.

The main drawback, being total is not a __local__ property.
So this is far worst that re-organizing two cases in the switch because as a user you have done something. A pattern can be total or not depending if someone change the return type of the method you are switching on (this methods can be in another module) or change the class hierarchy (again, the hierarchy can be defined in another module), the code stay exactly the same but the semantics is changed.

Adding an interface to a class should be harmless, but if this class is a case in a switch, this is changing the semantics of the switch. What can be worst in term of property ?
(again, C# doesn't have this issue because it is using "var" so the semantics is stable even if the type switched on change).

> 
>> I prefer this semantics, because it's local, the last case allows
>> null, it doesn't depends on the type of v or the relationship between
>> A and B.
> 
> This proposal seems entirely motivated by "let's have a really simple
> rule", rather than based on principles of what actually should happen in
> real programs or what programs should be expressible.  And, regardless
> of motivation, it is surely the wrong answer.

You can not in the same mail said that it's good goal "that switches and if-else chains of instanceof tests on the same target be refactorable to each other to the extent possible." and ends with "This proposal seems entirely motivated by ..., rather than based on principles of what actually should happen in real programs".

The semantics i'm proposing is based on real codes:
A switch like this
   switch (v) {
       case String s: A
       case Long l: B
       case Object o: C
   }
can be seen as
   if (v instanceof String s) {
    A
   } else if (v instanceof Long l) {
    B
   } else {
     var o = (Object) v;   // <--- cast here
     C
   }

It's a cut and paste from the mail you're answering.

Rémi




More information about the amber-spec-experts mailing list