Re: [External] : Re: Reviewing feedback on patterns in switch

2022-02-16 Thread Guy Steele


> On Feb 16, 2022, at 9:57 AM, Brian Goetz  wrote:
> . . .
> What if I want to use a partial pattern, and then customize either the 
> throwing part or provide default values?   I can provide an else clause:
> 
> Object o = ...
> let String s = o
> else throw new NotStringException();
> 
> or
> 
> Object o = ...
> let String s = o
> else { s = "no string"; }

Thanks for these examples; I had forgotten exactly what was previous proposed.

> Reminder: THIS EXPLANATION WAS PROVIDED SOLELY TO CLARIFY THE "FUTURE 
> CONSTRUCT" COMMENT IN THE && DISCUSSION.  

YES; I’M DONE NOW, THANKS!  :-)




Re: [External] : Re: Reviewing feedback on patterns in switch

2022-02-16 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, February 16, 2022 5:03:19 PM
> Subject: Re: [External] : Re: Reviewing feedback on patterns in switch

> Of course, in an ecosystem as diverse as Java developers, one routinely 
> expects
> to get complaints about both X and ~X. Which makes it notable that we have not
> gotten any complaints about "why do you force me to write an empty default".
> (I'm not complaining!)

> The case you raise -- legacy { switch type, labels, statement } switches -- is
> harder to fix. The things we've explored (like an opt-in to totality) are
> pretty poor fixes, since (a) they are noisy warts, and (b) people will forget
> them and still have the problem. So these are harder, longer-term problems.
> (For now, the best we can do is noisy warnings.)
The problem of the noisy warning is that there is no "right" way to fix the 
warning now. 
Adding a default is not what we want, we need a way to opt-in exhaustiveness. 
But we never agree on the way to do that. 

Rémi 

> On 2/16/2022 11:00 AM, Remi Forax wrote:

>>> From: "Brian Goetz" [ mailto:brian.go...@oracle.com | 
>>>  ]
>>> To: "amber-spec-experts" [ mailto:amber-spec-experts@openjdk.java.net |
>>>  ]
>>> Sent: Wednesday, February 16, 2022 4:49:19 PM
>>> Subject: Re: Reviewing feedback on patterns in switch

>>> One thing that we have, perhaps surprisingly, *not* gotten feedback on is
>>> forcing all non-legacy switches (legacy type, legacy labels, statement 
>>> only) to
>>> be exhaustive. I would have thought people would complain about pattern
>>> switches needing to be exhaustive, but no one has! So either no one has 
>>> tried
>>> it, or we got away with it...
>> Yes, we had several feedbacks about the opposite, why the switch statement 
>> on an
>> enum is not exhaustive, i.e. why the following code does not compile

>> enum Color { RED , BLUE } int x;
>> Color color = null ; switch (color) { case RED -> x = 0 ; case BLUE -> x = 1 
>> ;
>> }
>> System. out .println(x);  // x may not be initialized
>> Rémi

>>> On 1/25/2022 2:46 PM, Brian Goetz wrote:

>>>> We’ve previewed patterns in switch for two rounds, and have received some
>>>> feedback.  Overall, things work quite well, but there were a few items 
>>>> which
>>>> received some nontrivial feedback, and I’m prepared to suggest some changes
>>>> based on them.  I’ll summarize them here and create a new thread for each 
>>>> with
>>>> a more detailed description.

>>>> I’ll make a call for additional items a little later; for now, let’s focus 
>>>> on
>>>> these items before adding new things (or reopening old ones.)

>>>> 1.  Treatment of total patterns in switch / instanceof

>>>> 2.  Positioning of guards

>>>> 3.  Type refinements for GADTs

>>>> 4.  Diamond for type patterns (and record patterns)


Re: [External] : Re: Reviewing feedback on patterns in switch

2022-02-16 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, February 16, 2022 3:57:39 PM
> Subject: Re: [External] : Re: Reviewing feedback on patterns in switch

> OK, I'll make you a deal: I'll answer your question about let/bind, under the
> condition that we not divert the discussion on that right now -- there'll be a
> proper writeup soon. The answer here is entirely for context.

> If you don't agree, stop reading now :)
I think it's wiser to delay the discussion about let for later :) 

Rémi 

> On 2/15/2022 5:58 PM, Remi Forax wrote:

>>> - There are future constructs that may take patterns, and may (or may not) 
>>> want
>>> to express guard-like behavior, such as `let` statements (e.g., let .. when 
>>> ..
>>> else.) Expressing guards here with && is even less evocative of "guard
>>> condition" than it is with switches.
>> It's not clear to me how to use "let when else". Is it more like a ?: in C 
>> than
>> the let in in Caml ?

> The simplest form of `let` is a statement that takes a total pattern:

> let Point(var x, var y) = aPoint;

> and introduces bindings x and y into the remainder of the block. When
> applicable, this is better than a conditional context because (a) you get type
> checking for totality, and (b) you don't indent the rest of your method inside
> a test that you know will always succeed.

> If the pattern is total but has some remainder, the construct must throw on 
> the
> remainder, to preserve the invariant that when a `let` statement completes
> normally, all bindings are DA.

> What if I want to use a partial pattern, and then customize either the 
> throwing
> part or provide default values? I can provide an else clause:

> Object o = ...
> let String s = o
> else throw new NotStringException();

> or

> Object o = ...
> let String s = o
> else { s = "no string"; }

> These are two ways to preserve the "DA on normal completion" invariant; either
> by not completing normally, or by ensuring the bindings are DA.

> Now, we are in a situation where we are with switch: patterns do not express 
> all
> possible conditions. Which is why we introduced guards to switches. And we can
> use the same trick here:

> Object o = ...
> let String s = o
> when (!s.isEmpty())
> else { s = "no string"; }

> If we tried to use && here, it would look like

> Object o = ...
> let String s = o && (!s.isEmpty())
> else { s = "no string"; }

> which has the same problem as `case false && false`.

> Reminder: THIS EXPLANATION WAS PROVIDED SOLELY TO CLARIFY THE "FUTURE 
> CONSTRUCT"
> COMMENT IN THE && DISCUSSION.


Re: [External] : Re: Reviewing feedback on patterns in switch

2022-02-16 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, February 16, 2022 3:48:14 PM
> Subject: Re: [External] : Re: Reviewing feedback on patterns in switch

>> Not sure it's a no-brainer.
>> The question is more a question of consistency. There are two consistencies 
>> and
>> we have to choose one, either switch never allows null by default and users
>> have to opt-in with case null or we want patterns to behave the same way if
>> they are declared at top-level or if they are nested. I would say that the
>> semantics you propose is more like the current Java and the other semantics 
>> is
>> more like the Java of a future (if we choose the second option).

> You are right that any justification involving "for consistency" is mostly a
> self-justification. But here's where I think this is a cleaner decomposition.

> We define the semantics of the patterns in a vacuum; matching is a three-place
> predicate involving a static target type, a target expression, and a pattern.
> Null is not special here. (This is how we've done this all along.)

> Pattern contexts (instanceof, switch, and in the future, nested patterns,
> let/bind, catch, etc) on the other hand, may have pre-existing (and in some
> cases reasonable) opinions about null. What's new here is to fully separate 
> the
> construct opinions about special values from the pattern semantics -- the
> construct makes its decision about the special values, before consulting the
> pattern.

> This lets instanceof treat null as valid but say "null is not an instance of
> anything", past-switch treats null as always an error, and future-switch 
> treats
> null as a value you can opt into matching with the `null` label. (Yes, this is
> clunky; if we had non-nullable type patterns, we'd get there more directly.)

> But the part that I think is more or less obvious-in-hindsight is that the
> switch opinions are switches opinions, and the pattern opinions are pattern
> opinions, and there is a well-defined order in which those opinions are acted
> on -- the construct mediates between the target and the patterns. That is, we
> compose the result from the construct semantics and-then the pattern 
> semantics.
I think it will be more clear when we will introduce patterns on local variable 
declaration because those will only allow some patterns but not all. 

> None of this is really all that much about "how do people like it". But what I
> do think people will like is that they get a simple rule out of switches:
> "switches throw on null unless the letters n-u-l-l appear in the switch body".
> And a simple rule for instanceof: "instanceof never evaluates to true on 
> null".
> And that these rules are *independent of patterns*. So switch and instanceof
> can be understood separately from patterns.
It's not about how people like it but how people rationalize it. You can say 
"switches throw on null unless the letters n-u-l-l appear in the switch body" 
or "switches throw on null unless a null-friendly pattern appear in the switch 
body and this is also true for nested patterns". 
Both are valid approach. 

Rémi 


Re: Reviewing feedback on patterns in switch

2022-02-16 Thread Mark Raynsford
On 2022-02-16T10:49:19 -0500
Brian Goetz  wrote:

> One thing that we have, perhaps surprisingly, *not* gotten feedback on 
> is forcing all non-legacy switches (legacy type, legacy labels, 
> statement only) to be exhaustive.  I would have thought people would 
> complain about pattern switches needing to be exhaustive, but no one 
> has! So either no one has tried it, or we got away with it...
> 

I tried it and liked it. It didn't go unnoticed. :)

-- 
Mark Raynsford | https://www.io7m.com



Re: Reviewing feedback on patterns in switch

2022-02-16 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Wednesday, February 16, 2022 4:49:19 PM
> Subject: Re: Reviewing feedback on patterns in switch

> One thing that we have, perhaps surprisingly, *not* gotten feedback on is
> forcing all non-legacy switches (legacy type, legacy labels, statement only) 
> to
> be exhaustive. I would have thought people would complain about pattern
> switches needing to be exhaustive, but no one has! So either no one has tried
> it, or we got away with it...
Yes, we had several feedbacks about the opposite, why the switch statement on 
an enum is not exhaustive, i.e. why the following code does not compile 

enum Color { RED , BLUE } 
int x; 
Color color = null ; 
switch (color) { 
case RED -> x = 0 ; 
case BLUE -> x = 1 ; 
} 
System. out .println(x);  // x may not be initialized 
Rémi 

> On 1/25/2022 2:46 PM, Brian Goetz wrote:

>> We’ve previewed patterns in switch for two rounds, and have received some
>> feedback.  Overall, things work quite well, but there were a few items which
>> received some nontrivial feedback, and I’m prepared to suggest some changes
>> based on them.  I’ll summarize them here and create a new thread for each 
>> with
>> a more detailed description.

>> I’ll make a call for additional items a little later; for now, let’s focus on
>> these items before adding new things (or reopening old ones.)

>> 1.  Treatment of total patterns in switch / instanceof

>> 2.  Positioning of guards

>> 3.  Type refinements for GADTs

>> 4.  Diamond for type patterns (and record patterns)


Re: Reviewing feedback on patterns in switch

2022-02-16 Thread forax
> From: "Brian Goetz" 
> To: "Guy Steele" , "Remi Forax" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, February 16, 2022 4:05:35 PM
> Subject: Re: Reviewing feedback on patterns in switch

>>> For me, && is more natural than "when" because i've written more switch that
>>> uses && than "when".
>>> And don't forget that unlike most of the code, with pattern matching the 
>>> number
>>> of characters does matter, this is more similar to lambdas, if what you 
>>> write
>>> is too verbose, you will not write it.

>> At the risk of premature bikeshedding, have we already discussed and 
>> discarded
>> the idea of spelling “when” as “if”? It’s been a long time, and I forget.
> There was not extensive discussion on this, and its all very
> subjective/handwavy/"what we think people would think", but I remember a few
> comments on this:

> - The generality of "if" reminded people of the Perl-style "statement unless
> condition" postfix convention, and that people might see it as an
> "inconsistency" that they could not then say

> x = 3 if (condition);

> which is definitely somewhere we don't want to go.

> - We're use to seeing "if" with a consequence, and a "naked" if might have the
> effect of "lookahead pollution" in our mental parsers.

> - Keeping `if` for statements allows us to keep the "body" of case clauses
> visually distinct from the "envelope":

> case Foo(var x)
> if (x > 3) : if (x > 10) { ... }

> would make people's eyes go buggy. One could argue that "when" is not
> fantastically better:

> case Foo(var x)
> when (x > 3) : if (x > 10) { ... }

> but it doesn't take quite as long to de-bug oneself in that case.
And also the if stis followed by parenthesis and there is no need of 
parenthesis for a guard. 
So either people will always put parenthesis after if as a guard or be mystify 
that parenthesis are not required for a guard but required for the if 
statement. 

Rémi 


Re: [External] : Re: Reviewing feedback on patterns in switch

2022-02-16 Thread Brian Goetz
Of course, in an ecosystem as diverse as Java developers, one routinely 
expects to get complaints about both X and ~X.  Which makes it notable 
that we have not gotten any complaints about "why do you force me to 
write an empty default".  (I'm not complaining!)


The case you raise -- legacy { switch type, labels, statement } switches 
-- is harder to fix.  The things we've explored (like an opt-in to 
totality) are pretty poor fixes, since (a) they are noisy warts, and (b) 
people will forget them and still have the problem.  So these are 
harder, longer-term problems.  (For now, the best we can do is noisy 
warnings.)


On 2/16/2022 11:00 AM, Remi Forax wrote:





*From: *"Brian Goetz" 
*To: *"amber-spec-experts" 
*Sent: *Wednesday, February 16, 2022 4:49:19 PM
*Subject: *Re: Reviewing feedback on patterns in switch

One thing that we have, perhaps surprisingly, *not* gotten
feedback on is forcing all non-legacy switches (legacy type,
legacy labels, statement only) to be exhaustive.  I would have
thought people would complain about pattern switches needing to be
exhaustive, but no one has! So either no one has tried it, or we
got away with it...


Yes, we had several feedbacks about the opposite, why the switch 
statement on an enum is not exhaustive, i.e. why the following code 
does not compile


enum Color {RED,BLUE }
int x;
Color color =null;
switch (color) {
   case RED -> x =0;
   case BLUE -> x =1;
}
System.out.println(x);  // x may not be initialized
Rémi



On 1/25/2022 2:46 PM, Brian Goetz wrote:

We’ve previewed patterns in switch for two rounds, and have received 
some feedback.  Overall, things work quite well, but there were a few items 
which received some nontrivial feedback, and I’m prepared to suggest some 
changes based on them.  I’ll summarize them here and create a new thread for 
each with a more detailed description.

I’ll make a call for additional items a little later; for now, let’s 
focus on these items before adding new things (or reopening old ones.)

1.  Treatment of total patterns in switch / instanceof

2.  Positioning of guards

3.  Type refinements for GADTs

4.  Diamond for type patterns (and record patterns)






Re: Reviewing feedback on patterns in switch

2022-02-16 Thread Brian Goetz
One thing that we have, perhaps surprisingly, *not* gotten feedback on 
is forcing all non-legacy switches (legacy type, legacy labels, 
statement only) to be exhaustive.  I would have thought people would 
complain about pattern switches needing to be exhaustive, but no one 
has! So either no one has tried it, or we got away with it...


On 1/25/2022 2:46 PM, Brian Goetz wrote:

We’ve previewed patterns in switch for two rounds, and have received some 
feedback.  Overall, things work quite well, but there were a few items which 
received some nontrivial feedback, and I’m prepared to suggest some changes 
based on them.  I’ll summarize them here and create a new thread for each with 
a more detailed description.

I’ll make a call for additional items a little later; for now, let’s focus on 
these items before adding new things (or reopening old ones.)

1.  Treatment of total patterns in switch / instanceof

2.  Positioning of guards

3.  Type refinements for GADTs

4.  Diamond for type patterns (and record patterns)




Re: Reviewing feedback on patterns in switch

2022-02-16 Thread Brian Goetz


For me, && is more natural than "when" because i've written more 
switch that uses && than "when".
And don't forget that unlike most of the code, with pattern matching 
the number of characters does matter, this is more similar to 
lambdas, if what you write is too verbose, you will not write it.


At the risk of premature bikeshedding, have we already discussed and 
discarded the idea of spelling “when” as “if”? It’s been a long time, 
and I forget.


There was not extensive discussion on this, and its all very 
subjective/handwavy/"what we think people would think", but I remember a 
few comments on this:


 - The generality of "if" reminded people of the Perl-style "statement 
unless condition" postfix convention, and that people might see it as an 
"inconsistency" that they could not then say


   x = 3 if (condition);

which is definitely somewhere we don't want to go.


 - We're use to seeing "if" with a consequence, and a "naked" if might 
have the effect of "lookahead pollution" in our mental parsers.



 - Keeping `if` for statements allows us to keep the "body" of case 
clauses visually distinct from the "envelope":


    case Foo(var x)
    if (x > 3) : if (x > 10) { ... }

would make people's eyes go buggy.  One could argue that "when" is not 
fantastically better:


    case Foo(var x)
    when (x > 3) : if (x > 10) { ... }

but it doesn't take quite as long to de-bug oneself in that case.

On 2/15/2022 9:55 PM, Guy Steele wrote:


Re: [External] : Re: Reviewing feedback on patterns in switch

2022-02-16 Thread Brian Goetz
OK, I'll make you a deal: I'll answer your question about let/bind, 
under the condition that we not divert the discussion on that right now 
-- there'll be a proper writeup soon.  The answer here is entirely for 
context.


If you don't agree, stop reading now :)

On 2/15/2022 5:58 PM, Remi Forax wrote:


 - There are future constructs that may take patterns, and may (or
may not) want to express guard-like behavior, such as `let`
statements (e.g., let .. when .. else.)  Expressing guards here
with && is even less evocative of "guard condition" than it is
with switches.


It's not clear to me how to use "let when else". Is it more like a ?: 
in C than the let in in Caml ?


The simplest form of `let` is a statement that takes a total pattern:

    let Point(var x, var y) = aPoint;

and introduces bindings x and y into the remainder of the block. When 
applicable, this is better than a conditional context because (a) you 
get type checking for totality, and (b) you don't indent the rest of 
your method inside a test that you know will always succeed.


If the pattern is total but has some remainder, the construct must throw 
on the remainder, to preserve the invariant that when a `let` statement 
completes normally, all bindings are DA.


What if I want to use a partial pattern, and then customize either the 
throwing part or provide default values?   I can provide an else clause:


    Object o = ...
    let String s = o
    else throw new NotStringException();

or

    Object o = ...
    let String s = o
    else { s = "no string"; }

These are two ways to preserve the "DA on normal completion" invariant; 
either by not completing normally, or by ensuring the bindings are DA.


Now, we are in a situation where we are with switch: patterns do not 
express all possible conditions.  Which is why we introduced guards to 
switches.  And we can use the same trick here:


    Object o = ...
    let String s = o
    when (!s.isEmpty())
    else { s = "no string"; }

If we tried to use && here, it would look like

    Object o = ...
    let String s = o && (!s.isEmpty())
    else { s = "no string"; }

which has the same problem as `case false && false`.

Reminder: THIS EXPLANATION WAS PROVIDED SOLELY TO CLARIFY THE "FUTURE 
CONSTRUCT" COMMENT IN THE && DISCUSSION.




Re: [External] : Re: Reviewing feedback on patterns in switch

2022-02-16 Thread Brian Goetz



Not sure it's a no-brainer.
The question is more a question of consistency. There are two 
consistencies and we have to choose one, either switch never allows 
null by default and users have to opt-in with case null or we want 
patterns to behave the same way if they are declared at top-level or 
if they are nested. I would say that the semantics you propose is more 
like the current Java and the other semantics is more like the Java of 
a future (if we choose the second option).


You are right that any justification involving "for consistency" is 
mostly a self-justification.  But here's where I think this is a cleaner 
decomposition.


We define the semantics of the patterns in a vacuum; matching is a 
three-place predicate involving a static target type, a target 
expression, and a pattern.  Null is not special here.  (This is how 
we've done this all along.)


Pattern contexts (instanceof, switch, and  in the future, nested 
patterns, let/bind, catch, etc) on the other hand, may have pre-existing 
(and in some cases reasonable) opinions about null. What's new here is 
to fully separate the construct opinions about special values from the 
pattern semantics -- the construct makes its decision about the special 
values, before consulting the pattern.


This lets instanceof treat null as valid but say "null is not an 
instance of anything", past-switch treats null as always an error, and 
future-switch treats null as a value you can opt into matching with the 
`null` label.  (Yes, this is clunky; if we had non-nullable type 
patterns, we'd get there more directly.)


But the part that I think is more or less obvious-in-hindsight is that 
the switch opinions are switches opinions, and the pattern opinions are 
pattern opinions, and there is a well-defined order in which those 
opinions are acted on -- the construct mediates between the target and 
the patterns.  That is, we compose the result from the construct 
semantics and-then the pattern semantics.


None of this is really all that much about "how do people like it". But 
what I do think people will like is that they get a simple rule out of 
switches: "switches throw on null unless the letters n-u-l-l appear in 
the switch body".  And a simple rule for instanceof: "instanceof never 
evaluates to true on null".  And that these rules are *independent of 
patterns*.  So switch and instanceof can be understood separately from 
patterns.




Re: Reviewing feedback on patterns in switch

2022-02-15 Thread Guy Steele


On Feb 15, 2022, at 5:58 PM, Remi Forax 
mailto:fo...@univ-mlv.fr>> wrote:




From: "Brian Goetz" mailto:brian.go...@oracle.com>>
To: "amber-spec-experts" 
mailto:amber-spec-experts@openjdk.java.net>>
Sent: Tuesday, February 15, 2022 7:50:06 PM
Subject: Re: Reviewing feedback on patterns in switch
We're preparing a third preview of type patterns in switch.  Normally we would 
release after a second preview, but (a) we're about to get record patterns, 
which may disclose additional issues with switch, so best to keep it open for 
at least another round, and (b) we're proposing some nontrivial changes which 
deserve another preview.

Here's where we are on these.


1.  Treatment of total patterns in switch / instanceof

Quite honestly, in hindsight, I don't know why we didn't see this sooner; the 
incremental evolution proposed here is more principled than where we were in 
the previous round; now the construct (instanceof, switch, etc) *always* gets 
first crack at enforcing its nullity (and exception) opinions, and *then* 
delegates to the matching semantics of the pattern if it decides to do so.  
This fully separates pattern semantics from conditional construct semantics, 
rather than complecting them (which in turn deprived users of seeing the model 
more clearly.)  In hindsight, this is a no-brainer (which is why we preview 
things.)  We'll be addressing this in the 3rd preview.

Not sure it's a no-brainer.
The question is more a question of consistency. There are two consistencies and 
we have to choose one, either switch never allows null by default and users 
have to opt-in with case null or we want patterns to behave the same way if 
they are declared at top-level or if they are nested. I would say that the 
semantics you propose is more like the current Java and the other semantics is 
more like the Java of a future (if we choose the second option).

I think we should try the semantics you propose and see if people agree or not.

And I agree we should try these semantics.


2.  Positioning of guards

Making guards part of switch also feels like a better factoring than making 
them part of patterns; it simplifies patterns and totality, and puts switch on 
a more equal footing with our other conditional constructs.  We did go back and 
forth a few times on this, but having given this a few weeks to settle, I'm 
pretty convinced we'd regret going the other way.

There were two sub-points here: (a) is the guard part of the pattern or part of 
switch, and (b) the syntax.  There was general agreement on (a), but some had 
preference for && on (b).  I spent some more time thinking about this choice, 
and have come down firmly on the `when` side of the house as a result for a 
number of reasons.

Still agree on (a)



 - Possibility for ambiguity.  If switching over booleans (which we will surely 
eventually be forced into), locutions like `case false && false` will be very 
confusing; it's pure puzzler territory.
 - && has a stronger precedence than keyword-based operators like 
`instanceof`'; we want guards to be weakest here.

I don't understand your point, we want instanceof pattern && expression to be 
equivalent to instanceof type && expression + cast, so the fact that && has a 
stronger precedence makes that possible so it's not an issue.

 - Using && will confuse users about whether it is part of the expression, or 
part of the switch statement.  If we're deciding it is part of the switch, this 
should be clear, and a `when` clause makes that clear.

I don't think it's that important, apart if we start to also want to combine 
patterns with &&


 - There are future constructs that may take patterns, and may (or may not) 
want to express guard-like behavior, such as `let` statements (e.g., let .. 
when .. else.)  Expressing guards here with && is even less evocative of "guard 
condition" than it is with switches.

It's not clear to me how to use "let when else". Is it more like a ?: in C than 
the let in in Caml ?

That is what I understood the implication to be: something like

let User(var firstname, var lastName) = x when firstName.length() > 8 in
System.out.printf(“User with long first name”);
else System.out.printf(“Not a user, or user with a short first name”);

although this particular example could also be framed as

if (x instanceof User(var firstname, var lastName) && firstName.length() > 8)
System.out.printf(“User with long first name”);
else System.out.printf(“Not a user, or user with a short first name”);

so maybe I am misunderstanding something here, or have misremembered the 
proposal.

 - Users coming from other languages will find `case...when` quite clear.
 - We've talked about "targetless" switches as a possible future feature, which 
express multi-way conditionals:

switch {
  

Re: Reviewing feedback on patterns in switch

2022-02-15 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, February 15, 2022 7:50:06 PM
> Subject: Re: Reviewing feedback on patterns in switch

> We're preparing a third preview of type patterns in switch. Normally we would
> release after a second preview, but (a) we're about to get record patterns,
> which may disclose additional issues with switch, so best to keep it open for
> at least another round, and (b) we're proposing some nontrivial changes which
> deserve another preview.

> Here's where we are on these.

>> 1.  Treatment of total patterns in switch / instanceof

> Quite honestly, in hindsight, I don't know why we didn't see this sooner; the
> incremental evolution proposed here is more principled than where we were in
> the previous round; now the construct (instanceof, switch, etc) *always* gets
> first crack at enforcing its nullity (and exception) opinions, and *then*
> delegates to the matching semantics of the pattern if it decides to do so. 
> This
> fully separates pattern semantics from conditional construct semantics, rather
> than complecting them (which in turn deprived users of seeing the model more
> clearly.) In hindsight, this is a no-brainer (which is why we preview things.)
> We'll be addressing this in the 3rd preview.
Not sure it's a no-brainer. 
The question is more a question of consistency. There are two consistencies and 
we have to choose one, either switch never allows null by default and users 
have to opt-in with case null or we want patterns to behave the same way if 
they are declared at top-level or if they are nested. I would say that the 
semantics you propose is more like the current Java and the other semantics is 
more like the Java of a future (if we choose the second option). 

I think we should try the semantics you propose and see if people agree or not. 

>> 2.  Positioning of guards

> Making guards part of switch also feels like a better factoring than making 
> them
> part of patterns; it simplifies patterns and totality, and puts switch on a
> more equal footing with our other conditional constructs. We did go back and
> forth a few times on this, but having given this a few weeks to settle, I'm
> pretty convinced we'd regret going the other way.

> There were two sub-points here: (a) is the guard part of the pattern or part 
> of
> switch, and (b) the syntax. There was general agreement on (a), but some had
> preference for && on (b). I spent some more time thinking about this choice,
> and have come down firmly on the `when` side of the house as a result for a
> number of reasons.
Still agree on (a) 

> - Possibility for ambiguity. If switching over booleans (which we will surely
> eventually be forced into), locutions like `case false && false` will be very
> confusing; it's pure puzzler territory.
> - && has a stronger precedence than keyword-based operators like 
> `instanceof`';
> we want guards to be weakest here.
I don't understand your point, we want instanceof pattern && expression to be 
equivalent to instanceof type && expression + cast, so the fact that && has a 
stronger precedence makes that possible so it's not an issue. 

> - Using && will confuse users about whether it is part of the expression, or
> part of the switch statement. If we're deciding it is part of the switch, this
> should be clear, and a `when` clause makes that clear.
I don't think it's that important, apart if we start to also want to combine 
patterns with && 

> - There are future constructs that may take patterns, and may (or may not) 
> want
> to express guard-like behavior, such as `let` statements (e.g., let .. when ..
> else.) Expressing guards here with && is even less evocative of "guard
> condition" than it is with switches.
It's not clear to me how to use "let when else". Is it more like a ?: in C than 
the let in in Caml ? 

> - Users coming from other languages will find `case...when` quite clear.
> - We've talked about "targetless" switches as a possible future feature, which
> express multi-way conditionals:

> switch {
> case when (today() == TUESDAY): ...
> case when (location() == GREENLAND): ...
> ...
> }

> This would look quite silly with &&.
For me, this is like cond in Lisp but more verbose. using "case" and "when" 
here is sillly. 

> Similarly, one could mix guards with a targeted switch:

> switch (x) {
> case Time t: ...
> case Place p: ...
> default when (today() == TUESDAY): ... tuesday-specific default
> default: ... regular default ...
default && today() == TUESDAY is fine for me. 

> Expressing guards that are the whole condition with `when` is much more 
> natural
>

Re: Reviewing feedback on patterns in switch

2022-02-15 Thread Brian Goetz
We're preparing a third preview of type patterns in switch.  Normally we 
would release after a second preview, but (a) we're about to get record 
patterns, which may disclose additional issues with switch, so best to 
keep it open for at least another round, and (b) we're proposing some 
nontrivial changes which deserve another preview.


Here's where we are on these.


1.  Treatment of total patterns in switch / instanceof


Quite honestly, in hindsight, I don't know why we didn't see this 
sooner; the incremental evolution proposed here is more principled than 
where we were in the previous round; now the construct (instanceof, 
switch, etc) *always* gets first crack at enforcing its nullity (and 
exception) opinions, and *then* delegates to the matching semantics of 
the pattern if it decides to do so.  This fully separates pattern 
semantics from conditional construct semantics, rather than complecting 
them (which in turn deprived users of seeing the model more clearly.)  
In hindsight, this is a no-brainer (which is why we preview things.)  
We'll be addressing this in the 3rd preview.



2.  Positioning of guards


Making guards part of switch also feels like a better factoring than 
making them part of patterns; it simplifies patterns and totality, and 
puts switch on a more equal footing with our other conditional 
constructs.  We did go back and forth a few times on this, but having 
given this a few weeks to settle, I'm pretty convinced we'd regret going 
the other way.


There were two sub-points here: (a) is the guard part of the pattern or 
part of switch, and (b) the syntax.  There was general agreement on (a), 
but some had preference for && on (b).  I spent some more time thinking 
about this choice, and have come down firmly on the `when` side of the 
house as a result for a number of reasons.


 - Possibility for ambiguity.  If switching over booleans (which we 
will surely eventually be forced into), locutions like `case false && 
false` will be very confusing; it's pure puzzler territory.
 - && has a stronger precedence than keyword-based operators like 
`instanceof`'; we want guards to be weakest here.
 - Using && will confuse users about whether it is part of the 
expression, or part of the switch statement.  If we're deciding it is 
part of the switch, this should be clear, and a `when` clause makes that 
clear.
 - There are future constructs that may take patterns, and may (or may 
not) want to express guard-like behavior, such as `let` statements 
(e.g., let .. when .. else.)  Expressing guards here with && is even 
less evocative of "guard condition" than it is with switches.

 - Users coming from other languages will find `case...when` quite clear.
 - We've talked about "targetless" switches as a possible future 
feature, which express multi-way conditionals:


    switch {
    case when (today() == TUESDAY): ...
    case when (location() == GREENLAND): ...
    ...
    }

This would look quite silly with &&.  Similarly, one could mix guards 
with a targeted switch:


    switch (x) {
    case Time t: ...
    case Place p: ...
    default when (today() == TUESDAY): ... tuesday-specific default
    default: ... regular default ...

Expressing guards that are the whole condition with `when` is much more 
natural than with &&.


tl;dr: inventing a `when` modifier on switch now will save us from 
having to invent something else in the future; choosing && will not.


We can continue to discuss the bikeshed at low volume (at least until we 
start repeating ourselves), but we need to address both of these points 
in the 3rd preview.



3.  Type refinements for GADTs


I've been working through the details here, and there are a number of 
additional touch points where GADTs can provide type refinement, not 
just on the RHS of a case, such as totality and inference.  I'll be 
pulling all these together to try to get a total picture here. It's not 
a blocker for the 3rd preview, it can be a future refinement.



4.  Diamond for type patterns (and record patterns)
This seems desirable, but there are details to work out.  It's not a 
blocker for the 3rd preview, it can be a future refinement.

Re: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-28 Thread Brian Goetz
You can say you only change the semantics of switch not the semantics 
of pattern matching, but the idea that you can separate the two is 
confusing.


From a mathematical point of view, it is quite clear.  We define a `x 
matches P` relation.  In this relation, `Object o` matches all values of 
x, including null.


Then, we define the semantics of `instanceof` and `switch`.  For 
example, `x instanceof P` means: "if x is null, then false, otherwise 
evaluates to `x matches P`."  The construct gets to decide when to 
evaluate the pattern.


This is just like how we separate the inference machinery from how 
inference is used (differently) to produce a result for diamond or var.


What you're saying, I think, is that most users don't separate the 
layers like this; their understanding of pattern matching is conflated 
with how pattern contexts like switch/instanceof work. And that is 
surely true.  But having a clear definition of how pattern matching 
works, and a clear definition of how switch/instanceof use pattern 
matching, allows the users who *do* want to understand, to do so more 
easily, because we've separated the concepts.


PS: the feedback about the fact that it's not clear if a switch allows 
null or not can also be seen as a symptom of the fact that the notion 
of total pattern is not obvious for everybody (and having no syntax 
hint does not help).


I think this is the real issue; leaning on totality is more sound and 
less ad-hoc, but harder to learn.  You'd like to make that easier to 
learn by introducing more syntax; I'm saying that this is (a) more 
complicated in the long run, and (b) way over-rotating towards treatment 
of null.


Re: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-28 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "Tagir Valeev" , "amber-spec-experts"
> 
> Sent: Thursday, January 27, 2022 4:41:27 PM
> Subject: Re: [External] : Re: Treatment of total patterns (was: Reviewing
> feedback on patterns in switch)

>> In that case, i prefer the current semantics because it's the same if a 
>> pattern
>> is a top-level or not.

> I wish people could keep these things straight. We’re not talking about 
> changing
> the semantics of how pattern matching works, which patterns match what, what
> nesting means, etc. We’re simply talking about the *boundary* between a
> specific pattern-accepting construct, which has pre-existing value filtering
> opinions, and the patterns it accepts.

> The current (preview) boundary says:

> - If a switch has a `case null`, or a total pattern, a null value matches 
> that,
> otherwise we throw NPE on null, and for non-null, it is matched to the 
> patterns
> in the case labels.

> The adjusted boundary says:

> - If a switch has a `case null`, a null value matches that, otherwise we throw
> NPE on null, and for non-null, it is matched to the patterns in the case 
> label.

> So this adjusts *which* patterns the switch lets see null values. Previously, 
> it
> was “none”; in the current preview, it is “case null and total patterns”, and
> the adjustment proposed is “case null”. The latter is a tradeoff to avoid
> confusing the users, who currently believe switch always throws on null, by
> saying “switch accepts null if it says case null.”

> We currently have a similar problem with `intsnaceof`, where we disallow total
> patterns on the RHS of instanceof. We would adjust in the same way: instanceof
> always says false on nulls, and tests against the RHS on non-null.

> Nothing to do with the semantics of pattern matching. Total patterns are still
> total.

You can say you only change the semantics of switch not the semantics of 
pattern matching, but the idea that you can separate the two is confusing. 

For me, the semantics of pattern matching change because currently a total 
pattern always match null, whatever its position, as top-level or inside a 
record pattern (for example), 
with the semantics you propose a top-level pattern will not match null anymore 
but will match null if nested. 

So yes, i suppose you can say that the semantics of a total pattern is not 
changed because whatever the position it *can* match null, but 'm not sure this 
way of thinking helps. 

To make thing super clear, with the current semantics, "case Object o" always 
match null, with your proposal, the answer is it depends if it is nested or 
not. 
That's why i prefer the current semantics. 

regards, 
Rémi 

PS: the feedback about the fact that it's not clear if a switch allows null or 
not can also be seen as a symptom of the fact that the notion of total pattern 
is not obvious for everybody (and having no syntax hint does not help). 


Re: Reviewing feedback on patterns in switch

2022-01-28 Thread Remi Forax
- Original Message -
> From: "mark" 
> To: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Sent: Friday, January 28, 2022 12:43:55 PM
> Subject: Re: Reviewing feedback on patterns in switch

> On 2022-01-25T19:46:09 +
> Brian Goetz  wrote:
> 
>> We’ve previewed patterns in switch for two rounds, and have received some
>> feedback.  Overall, things work quite well, but there were a few items which
>> received some nontrivial feedback, and I’m prepared to suggest some changes
>> based on them.  I’ll summarize them here and create a new thread for each 
>> with
>> a more detailed description.
>> 
>> I’ll make a call for additional items a little later; for now, let’s focus on
>> these items before adding new things (or reopening old ones.)
>> 
>> 1.  Treatment of total patterns in switch / instanceof
>> 
>> 2.  Positioning of guards
>> 
>> 3.  Type refinements for GADTs
>> 
>> 4.  Diamond for type patterns (and record patterns)
> 
> Hello!
> 
> I'm a little late to the party, as ever, but is there a specific build I
> should be looking at so that I can get a better idea of what the current
> state of things are?

Hi Mark,
the last jdk18 build or any new jdk19 builds will do.

> 
> --
> Mark Raynsford | https://www.io7m.com


Rémi


Re: Reviewing feedback on patterns in switch

2022-01-28 Thread Mark Raynsford
On 2022-01-25T19:46:09 +
Brian Goetz  wrote:

> We’ve previewed patterns in switch for two rounds, and have received some 
> feedback.  Overall, things work quite well, but there were a few items which 
> received some nontrivial feedback, and I’m prepared to suggest some changes 
> based on them.  I’ll summarize them here and create a new thread for each 
> with a more detailed description.  
> 
> I’ll make a call for additional items a little later; for now, let’s focus on 
> these items before adding new things (or reopening old ones.)  
> 
> 1.  Treatment of total patterns in switch / instanceof
> 
> 2.  Positioning of guards
> 
> 3.  Type refinements for GADTs
> 
> 4.  Diamond for type patterns (and record patterns)

Hello!

I'm a little late to the party, as ever, but is there a specific build I
should be looking at so that I can get a better idea of what the current
state of things are?

-- 
Mark Raynsford | https://www.io7m.com



Re: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-27 Thread Brian Goetz


In that case, i prefer the current semantics because it's the same if a pattern 
is a top-level or not.


I wish people could keep these things straight.  We’re not talking about 
changing the semantics of how pattern matching works, which patterns match 
what, what nesting means, etc.  We’re simply talking about the *boundary* 
between a specific pattern-accepting construct, which has pre-existing value 
filtering opinions, and the patterns it accepts.

The current (preview) boundary says:

 - If a switch has a `case null`, or a total pattern, a null value matches 
that, otherwise we throw NPE on null, and for non-null, it is matched to the 
patterns in the case labels.

The adjusted boundary says:

 - If a switch has a `case null`, a null value matches that, otherwise we throw 
NPE on null, and for non-null, it is matched to the patterns in the case label.

So this adjusts *which* patterns the switch lets see null values.  Previously, 
it was “none”; in the current preview, it is “case null and total patterns”, 
and the adjustment proposed is “case null”.  The latter is a tradeoff to avoid 
confusing the users, who currently believe switch always throws on null, by 
saying “switch accepts null if it says case null.”

We currently have a similar problem with `intsnaceof`, where we disallow total 
patterns on the RHS of instanceof.  We would adjust in the same way: instanceof 
always says false on nulls, and tests against the RHS on non-null.

Nothing to do with the semantics of pattern matching.  Total patterns are still 
total.


Re: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch)

2022-01-27 Thread Brian Goetz
OK, I get your point now.  Your concern is not about *inference*, but 
specifically how *diamond* will snap to the bound when it infers a wildcard, 
because `new` doesn’t allow wildcards.  But we do something differently for 
inferring type variables of generic methods (we’ll gladly infer a capture) or 
locals (we’ll project a capture to a nearby super type without capture.)

On Jan 27, 2022, at 8:20 AM, fo...@univ-mlv.fr<mailto:fo...@univ-mlv.fr> wrote:




From: "Brian Goetz" mailto:brian.go...@oracle.com>>
To: "Remi Forax" mailto:fo...@univ-mlv.fr>>
Cc: "amber-spec-experts" 
mailto:amber-spec-experts@openjdk.java.net>>
Sent: Thursday, January 27, 2022 2:04:35 PM
Subject: Re: [External] : Re: Diamond in type patterns (was: Reviewing feedback 
on patterns in switch)
It's more an engineering thing here, we have far more casts than switch + 
pattern in existing code, and given that we suppose (perhaps wrongly) that the 
semantics of the inference is not exactly one already existing,

I’d like to drill into this supposition.  My supposition (maybe wrong) is that 
we already solved most of this when we did `var`, with upward projection.

To recap, we spent a lot of time with `var` on what to do about non-denotable 
types.  These included the null type (banned on the grounds of uselessness), 
intersection types (allowed), and capture types (sanitized with upward 
projection.)  The basic idea of upward projection is that when we infer 
List, we replace it with a super type that has no capture types, and get 
List out.  (There’s also a downward projection.)

Let’s start with your examples of where ordinary inference produces an 
undesirable result, and then evaluate whether either or the projections solves 
the problem?

I don't think current projections are enough because we may want the inference 
to insert a wildcard by itsef,
for example with
  Object o = ...
  var list = (List<>) o;

or maybe we should not try to infer such code.

Rémi




Re: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch)

2022-01-27 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "amber-spec-experts" 
> Sent: Thursday, January 27, 2022 2:04:35 PM
> Subject: Re: [External] : Re: Diamond in type patterns (was: Reviewing 
> feedback
> on patterns in switch)

>> It's more an engineering thing here, we have far more casts than switch +
>> pattern in existing code, and given that we suppose (perhaps wrongly) that 
>> the
>> semantics of the inference is not exactly one already existing,

> I’d like to drill into this supposition. My supposition (maybe wrong) is that 
> we
> already solved most of this when we did `var`, with upward projection.

> To recap, we spent a lot of time with `var` on what to do about non-denotable
> types. These included the null type (banned on the grounds of uselessness),
> intersection types (allowed), and capture types (sanitized with upward
> projection.) The basic idea of upward projection is that when we infer
> List, we replace it with a super type that has no capture types, and get
> List out. (There’s also a downward projection.)

> Let’s start with your examples of where ordinary inference produces an
> undesirable result, and then evaluate whether either or the projections solves
> the problem?

I don't think current projections are enough because we may want the inference 
to insert a wildcard by itsef, 
for example with 
Object o = ... 
var list = (List<>) o; 

or maybe we should not try to infer such code. 

Rémi 


Re: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch)

2022-01-27 Thread Brian Goetz
It's more an engineering thing here, we have far more casts than switch + 
pattern in existing code, and given that we suppose (perhaps wrongly) that the 
semantics of the inference is not exactly one already existing,

I’d like to drill into this supposition.  My supposition (maybe wrong) is that 
we already solved most of this when we did `var`, with upward projection.

To recap, we spent a lot of time with `var` on what to do about non-denotable 
types.  These included the null type (banned on the grounds of uselessness), 
intersection types (allowed), and capture types (sanitized with upward 
projection.)  The basic idea of upward projection is that when we infer 
List, we replace it with a super type that has no capture types, and get 
List out.  (There’s also a downward projection.)

Let’s start with your examples of where ordinary inference produces an 
undesirable result, and then evaluate whether either or the projections solves 
the problem?


Re: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch)

2022-01-27 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, January 26, 2022 3:34:21 PM
> Subject: Re: [External] : Re: Diamond in type patterns (was: Reviewing 
> feedback
> on patterns in switch)

>> I think we should figure out how it should work on cast and then we can 
>> happily
>> applied it on patterns.

> I’m happy to have the cast discussion happen concurrently, but right now, my
> priority is on patterns, as we’re already two previews into 
> patterns-in-switch.

remember, the move from preview to real feature should be only when we are 
ready 

> But I’m not ready to say “we can’t solve this for patterns unless we also 
> solve
> it for cast RIGHT NOW. So I agree with the goal (solve it everywhere,
> eventually) but not with the ordering constraint.

It's more an engineering thing here, we have far more casts than switch + 
pattern in existing code, and given that we suppose (perhaps wrongly) that the 
semantics of the inference is not exactly one already existing, 
i think we will get better result if we try to automatically transforms all 
existing casts using a parametrized type to diamond casts and see when the 
inference fails and why. 

Also this feature is fully orthogonal with the rest of the patterns because the 
diamond syntax in type pattern is an invalid syntax, so this feature can have 
it's own tempo. 

>> despite the syntax being the same, the diamond syntax, i don't think we can
>> reuse the same inference rules between the new diamond and the cast diamond.

> Understood. (This is why, for example, we introduced upward and downward
> projection when we did var, because the rules for inference were not what we
> wanted for var.) But before we go on to the details, are we agreed on the 
> goal?

Agree on the goal, but do you agree on the methodology i propose above. 

Rémi 


Re: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-27 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "Tagir Valeev" , "amber-spec-experts"
> 
> Sent: Wednesday, January 26, 2022 3:08:39 PM
> Subject: Re: [External] : Re: Treatment of total patterns (was: Reviewing
> feedback on patterns in switch)

> I don’t think its helpful to try and reopen these old and settled issues. I 
> get
> that you think null should have a larger syntactic presence in the language,
> and you’ve made those points plenty of times, but we’re not reopening whether
> `Object o` is total, or whether `var` is more than type inference. We’re
> focused here on the interaction between switch and patterns, precisely because
> switch comes to the table with pre-existing null hostilities. We are not going
> to distort the semantics of pattern matching just so we can extrapolate from
> how C switch worked; we’ve been over this too many times.
In that case, i prefer the current semantics because it's the same if a pattern 
is a top-level or not. 

Rémi 

>> On Jan 26, 2022, at 8:45 AM, [ mailto:fo...@univ-mlv.fr |
>> fo...@univ-mlv.fr ] wrote:

>>> From: "Brian Goetz" < [ mailto:brian.go...@oracle.com | 
>>> brian.go...@oracle.com ]
>>> >
>>> To: "Remi Forax" < [ mailto:fo...@univ-mlv.fr | fo...@univ-mlv.fr ] >
>>> Cc: "Tagir Valeev" < [ mailto:amae...@gmail.com | amae...@gmail.com ] >,
>>> "amber-spec-experts" < [ mailto:amber-spec-experts@openjdk.java.net |
>>> amber-spec-experts@openjdk.java.net ] >
>>> Sent: Wednesday, January 26, 2022 1:47:38 PM
>>> Subject: Re: [External] : Re: Treatment of total patterns (was: Reviewing
>>> feedback on patterns in switch)

>>> Heh, you are incrementally rediscovering exactly why we chose the original
>>> “total is total” rule; of all the possible treatments, it is the most 
>>> logically
>>> consistent. Welcome.

>>> In this case, however, switches must be total. So here, either D is total
>>> (perhaps with remainder), or B/C/D cover whatever the content of Box is, or 
>>> it
>>> doesn’’t compile. If there is remainder (which is likely to be null, but 
>>> which
>>> won’t happen with a type pattern, only when D is more complicated), and no
>>> later case handles Box(null), then the switch will NPE. We don’t know if
>>> Box(null) is matched by any of these cases, but we *do* know that we will 
>>> not
>>> arrive at the statement after the switch if the target was Box(null).

>> It's true that if you can observe the different side effects when the code is
>> run, and from that you may have an idea if case Box(D d) matches or not (and
>> prey that there is not a catch() in the middle),
>> but the bar is very low if you say that to understand a code you have to run 
>> it.

>>> The proposed change to top-level null hostility doesn’t affect that.

>> yes, that my point, having to run a code to understand it is a clue that the
>> semantics you propose or the Java 18 one are both equally bad.

>> Again, the C# semantics does not have such problem, if we suppose that the 
>> code
>> compiles then with the code below, d can not be null

>> switch(box) {
>> case Box(B b) -> { }
>> case Box(C c) -> { }
>> case Box(D d) -> { } // does not accept null
>> }

>> while with this code, d can be null

>> switch(box) {
>> case Box(B b) -> { }
>> case Box(C c) -> { }
>> case Box(var d) -> { } // accept null
>> }

>> Rémi

>>>> On Jan 26, 2022, at 2:53 AM, Remi Forax < [ mailto:fo...@univ-mlv.fr |
>>>> fo...@univ-mlv.fr ] > wrote:

>>>> We should go a step further, this also means that with

>>>> switch(box) {
>>>> case Box(B b) -> {}
>>>> case Box(C c) -> {}
>>>> case Box(D d) -> {}
>>>> }

>>>> we have no idea if the switch will accept Box(null) or not.

>>>> So the idea that a type behave differently if nested inside a pattern or 
>>>> not is
>>>> not a good one.


Re: [External] : Re: Patterns and GADTs (was: Reviewing feedback on patterns in switch)

2022-01-27 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, January 26, 2022 3:30:45 PM
> Subject: Re: [External] : Re: Patterns and GADTs (was: Reviewing feedback on
> patterns in switch)

> I don’t object to having something explicit in the code, but I do object to
> having that be unchecked. In the original example:

>>  T unbox(Node n) {
>> return switch (n) {
>> case A n -> n.t;
>> case B n -> n.s;
>> };
>> }
> we could cast `n,s` to T, but the compiler would have no reason to believe 
> that
> this is valid, so it would give us an unchecked warning. But the reality is, 
> we
> do have enough information to validate this. Now, I’m not sure if users would
> be any happier about

> case B n -> (T) n.s

> even if they did not get an unchecked warning. Still, a cast is probably 
> better
> than new, narrowly targeted syntax here.

> If we’re diffident about refining the type of T, we could consider an implicit
> conversion (String can be converted to T in a context where we’ve gathered the
> appropriate constraints on T), but this is more complicated, and I’m not sure
> users will find it any more understandable. Refining the type is something 
> that
> will make more sense to the user (“I know T is String here!”) than complex
> rules about when we can funge T and String.

I agree, for the compiler, it should be like adding a constraint T = String. 
The conversion is an equivalent of String <: T which is only half true. 

Let start without requiring a cast and see if it's too magical or not. 

Rémi 

>> On Jan 26, 2022, at 9:16 AM, [ mailto:fo...@univ-mlv.fr |
>> fo...@univ-mlv.fr ] wrote:

>>> From: "Brian Goetz" < [ mailto:brian.go...@oracle.com | 
>>> brian.go...@oracle.com ]
>>> >
>>> To: "Remi Forax" < [ mailto:fo...@univ-mlv.fr | fo...@univ-mlv.fr ] >
>>> Cc: "amber-spec-experts" < [ mailto:amber-spec-experts@openjdk.java.net |
>>> amber-spec-experts@openjdk.java.net ] >
>>> Sent: Wednesday, January 26, 2022 1:28:19 PM
>>> Subject: Re: [External] : Re: Patterns and GADTs (was: Reviewing feedback on
>>> patterns in switch)

>>>> The instanceof example is not a source backward compatible change, 
>>>> remember that
>>>> instanceof is not a preview feature.

>>> I’m well aware, can you give an example where flow typing of *type variables
>>> only* might lead to incompatibility? (I’m aware that this is a possibility, 
>>> but
>>> you’re stating it like its a fact already on the table.) For example, where 
>>> it
>>> would change overload selection (this is where flow typing for variables 
>>> falls
>>> down, among other places.)

>> sure,

>> sealed interface Node { }
>> record A(T t) implements Node { }
>> record B(String s) implements Node { }

>> void foo(Object o) { }
>> void foo(List list) { }

>>  void m() {
>> if (node instanceof B b) {
>> foo(List.of());
>> }
>> }

>> Rémi


Re: Reviewing feedback on patterns in switch

2022-01-26 Thread Brian Goetz
To summarize the feedback so far (please, don’t reply here, use the threads): 

> 1.  Treatment of total patterns in switch / instanceof

There seems to be overall inclination to adjust semantics of *switch* (not 
patterns) to be more in line with historical switch behavior.  This could be 
summarized as “if you want null, say null”, at least for switch, which we can 
justify because switch has a historical hostility to null.  The cost is that 
refactoring / code transform will be slightly less transparent.  Would be great 
to have a catalog of these.  

> 2.  Positioning of guards

There were two points here: whether a guard is attached to a pattern or to a 
case, and secondarily, how we denote a guard.  There seems to be overall 
agreement so far that attaching to the case is preferable; there is not yet 
agreement on syntax.

> 3.  Type refinements for GADTs

There’s some general feeling that we have a problem that needs to be solved, 
but some diffidence about refining type variables as a result of pattern 
matching.  

> 4.  Diamond for type patterns (and record patterns)

No one seems to object to the notion of diamond in patterns.  Some questions 
about “why didn’t we do implicit diamond” (as we did with method references.)  
Some questions about “are there other contexts we should be doing this in."


So far we’ve heard from Remi and Tagir.  Would like to hear from others.  




Re: [External] : Re: Patterns and GADTs (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread Brian Goetz
I don’t object to having something explicit in the code, but I do object to 
having that be unchecked.  In the original example:

   T unbox(Node n) {
  return switch (n) {
  case A n -> n.t;
  case B n -> n.s;
  };
  }

we could cast `n,s` to T, but the compiler would have no reason to believe that 
this is valid, so it would give us an unchecked warning.  But the reality is, 
we do have enough information to validate this.  Now, I’m not sure if users 
would be any happier about

case B n -> (T) n.s

even if they did not get an unchecked warning.  Still, a cast is probably 
better than new, narrowly targeted syntax here.

If we’re diffident about refining the type of T, we could consider an implicit 
conversion (String can be converted to T in a context where we’ve gathered the 
appropriate constraints on T), but this is more complicated, and I’m not sure 
users will find it any more understandable.  Refining the type is something 
that will make more sense to the user (“I know T is String here!”) than complex 
rules about when we can funge T and String.


On Jan 26, 2022, at 9:16 AM, fo...@univ-mlv.fr<mailto:fo...@univ-mlv.fr> wrote:




From: "Brian Goetz" mailto:brian.go...@oracle.com>>
To: "Remi Forax" mailto:fo...@univ-mlv.fr>>
Cc: "amber-spec-experts" 
mailto:amber-spec-experts@openjdk.java.net>>
Sent: Wednesday, January 26, 2022 1:28:19 PM
Subject: Re: [External] : Re: Patterns and GADTs (was: Reviewing feedback on 
patterns in switch)
The instanceof example is not a source backward compatible change, remember 
that instanceof is not a preview feature.

I’m well aware, can you give an example where flow typing of *type variables 
only* might lead to incompatibility?  (I’m aware that this is a possibility, 
but you’re stating it like its a fact already on the table.)  For example, 
where it would change overload selection (this is where flow typing for 
variables falls down, among other places.)

sure,

sealed interface Node { }
record A(T t) implements Node { }
record B(String s) implements Node { }

   void foo(Object o) { }
   void foo(List list) { }

   void m() {
if (node instanceof B b) {
  foo(List.of());
}
  }

Rémi





Re: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread Brian Goetz
I think we should figure out how it should work on cast and then we can happily 
applied it on patterns.

I’m happy to have the cast discussion happen concurrently, but right now, my 
priority is on patterns, as we’re already two previews into patterns-in-switch. 
 But I’m not ready to say “we can’t solve this for patterns unless we also 
solve it for cast RIGHT NOW.  So I agree with the goal (solve it everywhere, 
eventually) but not with the ordering constraint.

despite the syntax being the same, the diamond syntax, i don't think we can 
reuse the same inference rules between the new diamond and the cast diamond.

Understood.  (This is why, for example, we introduced upward and downward 
projection when we did var, because the rules for inference were not what we 
wanted for var.)  But before we go on to the details, are we agreed on the goal?



Re: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, January 26, 2022 1:23:04 PM
> Subject: Re: [External] : Re: Diamond in type patterns (was: Reviewing 
> feedback
> on patterns in switch)

>> The questions we did not answer the last time we talk about that subject
>> - why should we allow raw types here ?

> We already have a clear precedent with type patterns in instanceof — which is
> not a preview feature any more. So for one, now you’re talking about making a
> *change* to the existing language semantics. There are other concerns too.

>> - given that this is equivalent to an instanceof + cast, why we can not use
>> diamond inference on cast ?

> You’re not being clear about what you’re saying, you could be saying either of
> the following (or others):

> - You’re proposing diamond here, but not there, then your proposal is
> inconsistent, and therefore stupid.
> - I love your proposal, but I think we should additionally talk about other
> places to use diamond as well.

> I can’t tell which of these you’re saying, or maybe its something else?

I think we should figure out how it should work on cast and then we can happily 
applied it on patterns. 

>> - how this inference work ? Is is the same inference than with the diamond
>> constructor ?

> Again, I can’t tell whether you’re saying “this is dumb, it can’t work”, or
> “this is great, but I can’t figure out the details.”

The rules for what you can use as argument of a type parameter when doing a new 
and when doing a cast are not the same, 
for examples, 
new ArrayList() is not a valid code while (ArrayList) is a perfect valid 
cast, 
new ArrayList[3] is not a valid code while (ArrayList[]) may or 
may not be a valid cast, 
new ArrayList() is a valid code while (ArrayList) may or may 
not be a valid cast . 

despite the syntax being the same, the diamond syntax, i don't think we can 
reuse the same inference rules between the new diamond and the cast diamond. 

regards, 
Rémi 


Re: [External] : Re: Patterns and GADTs (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, January 26, 2022 1:28:19 PM
> Subject: Re: [External] : Re: Patterns and GADTs (was: Reviewing feedback on
> patterns in switch)

>> The instanceof example is not a source backward compatible change, remember 
>> that
>> instanceof is not a preview feature.

> I’m well aware, can you give an example where flow typing of *type variables
> only* might lead to incompatibility? (I’m aware that this is a possibility, 
> but
> you’re stating it like its a fact already on the table.) For example, where it
> would change overload selection (this is where flow typing for variables falls
> down, among other places.)

sure, 

sealed interface Node { } 
record A(T t) implements Node { } 
record B(String s) implements Node { } 

void foo(Object o) { } 
void foo(List list) { } 

 void m() { 
if (node instanceof B b) { 
foo(List.of()); 
} 
} 

Rémi 


Re: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread Brian Goetz
I don’t think its helpful to try and reopen these old and settled issues.  I 
get that you think null should have a larger syntactic presence in the 
language, and you’ve made those points plenty of times, but we’re not reopening 
whether `Object o` is total, or whether `var` is more than type inference.  
We’re focused here on the interaction between switch and patterns, precisely 
because switch comes to the table with pre-existing null hostilities.  We are 
not going to distort the semantics of pattern matching just so we can 
extrapolate from how C switch worked; we’ve been over this too many times.



On Jan 26, 2022, at 8:45 AM, fo...@univ-mlv.fr<mailto:fo...@univ-mlv.fr> wrote:




From: "Brian Goetz" mailto:brian.go...@oracle.com>>
To: "Remi Forax" mailto:fo...@univ-mlv.fr>>
Cc: "Tagir Valeev" mailto:amae...@gmail.com>>, 
"amber-spec-experts" 
mailto:amber-spec-experts@openjdk.java.net>>
Sent: Wednesday, January 26, 2022 1:47:38 PM
Subject: Re: [External] : Re: Treatment of total patterns (was: Reviewing 
feedback on patterns in switch)
Heh, you are incrementally rediscovering exactly why we chose the original 
“total is total” rule; of all the possible treatments, it is the most logically 
consistent.  Welcome.

In this case, however, switches must be total.  So here, either D is total 
(perhaps with remainder), or B/C/D cover whatever the content of Box is, or it 
doesn’’t compile.  If there is remainder (which is likely to be null, but which 
won’t happen with a type pattern, only when D is more complicated), and no 
later case handles Box(null), then the switch will NPE.  We don’t know if 
Box(null) is matched by any of these cases, but we *do* know that we will not 
arrive at the statement after the switch if the target was Box(null).

It's true that if you can observe the different side effects when the code is 
run, and from that you may have an idea if case Box(D d) matches or not (and 
prey that there is not a catch() in the middle),
but the bar is very low if you say that to understand a code you have to run it.


The proposed change to top-level null hostility doesn’t affect that.

yes, that my point, having to run a code to understand it is a clue that the 
semantics you propose or the Java 18 one are both equally bad.

Again, the C# semantics does not have such problem, if we suppose that the code 
compiles then with the code below, d can not be null

switch(box) {
  case Box(B b) -> { }
  case Box(C c) -> { }
  case Box(D d) -> { }  // does not accept null
}

while with this code, d can be null

switch(box) {
  case Box(B b) -> { }
  case Box(C c) -> { }
  case Box(var d) -> { }  // accept null
}


Rémi


On Jan 26, 2022, at 2:53 AM, Remi Forax 
mailto:fo...@univ-mlv.fr>> wrote:

We should go a step further, this also means that with

switch(box) {
  case Box(B b) -> {}
  case Box(C c) -> {}
  case Box(D d) -> {}
  }

we have no idea if the switch will accept Box(null) or not.

So the idea that a type behave differently if nested inside a pattern or not is 
not a good one.



Re: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread forax
> From: "Brian Goetz" 
> To: "Remi Forax" 
> Cc: "Tagir Valeev" , "amber-spec-experts"
> 
> Sent: Wednesday, January 26, 2022 1:47:38 PM
> Subject: Re: [External] : Re: Treatment of total patterns (was: Reviewing
> feedback on patterns in switch)

> Heh, you are incrementally rediscovering exactly why we chose the original
> “total is total” rule; of all the possible treatments, it is the most 
> logically
> consistent. Welcome.

> In this case, however, switches must be total. So here, either D is total
> (perhaps with remainder), or B/C/D cover whatever the content of Box is, or it
> doesn’’t compile. If there is remainder (which is likely to be null, but which
> won’t happen with a type pattern, only when D is more complicated), and no
> later case handles Box(null), then the switch will NPE. We don’t know if
> Box(null) is matched by any of these cases, but we *do* know that we will not
> arrive at the statement after the switch if the target was Box(null).

It's true that if you can observe the different side effects when the code is 
run, and from that you may have an idea if case Box(D d) matches or not (and 
prey that there is not a catch() in the middle), 
but the bar is very low if you say that to understand a code you have to run 
it. 

> The proposed change to top-level null hostility doesn’t affect that.

yes, that my point, having to run a code to understand it is a clue that the 
semantics you propose or the Java 18 one are both equally bad. 

Again, the C# semantics does not have such problem, if we suppose that the code 
compiles then with the code below, d can not be null 

switch(box) { 
case Box(B b) -> { } 
case Box(C c) -> { } 
case Box(D d) -> { } // does not accept null 
} 

while with this code, d can be null 

switch(box) { 
case Box(B b) -> { } 
case Box(C c) -> { } 
case Box(var d) -> { } // accept null 
} 

Rémi 

>> On Jan 26, 2022, at 2:53 AM, Remi Forax < [ mailto:fo...@univ-mlv.fr |
>> fo...@univ-mlv.fr ] > wrote:

>> We should go a step further, this also means that with

>> switch(box) {
>> case Box(B b) -> {}
>> case Box(C c) -> {}
>> case Box(D d) -> {}
>> }

>> we have no idea if the switch will accept Box(null) or not.

>> So the idea that a type behave differently if nested inside a pattern or not 
>> is
>> not a good one.


Re: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread Brian Goetz
Heh, you are incrementally rediscovering exactly why we chose the original 
“total is total” rule; of all the possible treatments, it is the most logically 
consistent.  Welcome.

In this case, however, switches must be total.  So here, either D is total 
(perhaps with remainder), or B/C/D cover whatever the content of Box is, or it 
doesn’’t compile.  If there is remainder (which is likely to be null, but which 
won’t happen with a type pattern, only when D is more complicated), and no 
later case handles Box(null), then the switch will NPE.  We don’t know if 
Box(null) is matched by any of these cases, but we *do* know that we will not 
arrive at the statement after the switch if the target was Box(null).  The 
proposed change to top-level null hostility doesn’t affect that.

So I’m not sure what your objection is?

On Jan 26, 2022, at 2:53 AM, Remi Forax 
mailto:fo...@univ-mlv.fr>> wrote:

We should go a step further, this also means that with

switch(box) {
  case Box(B b) -> {}
  case Box(C c) -> {}
  case Box(D d) -> {}
  }

we have no idea if the switch will accept Box(null) or not.

So the idea that a type behave differently if nested inside a pattern or not is 
not a good one.



Re: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread Brian Goetz
> I strongly support this change.
> 
> In my experience, it's much more important to have automatic
> refactorings between switch and chains of 'if' than between nested and
> flat switches.

Of course, this might be partially because we *have* chains of if else now, but 
no switches on nested patterns.  Still, I agree that the if-else refactor case 
is important.  To that point, a total pattern in a switch gets translated as 
`else` rather than `else if`, and it would be nice if the IDE recognized `case 
null, Object o` as being total in this case, and a null-hostile switch needs 
the implicit null check translated when going from switch -> if else chain.  

It would be great to write down the refactoring asymmetries in one place, just 
to see a “total" picture of how distortive any given treatment is?



Re: [External] : Re: Positioning of guards (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread Brian Goetz
> For the record: I like the current version with &&. It's short and
> easy to understand (as people already know what && means in Java). I
> see no reason in replacing it with `when`, which is more limiting.

I like the && syntax too (though we can invent some nasty puzzlers with 
booleans, such as `case false && false`, which are not so likable.)  But the 
two are not uncoupled; its harder to imagine && as part of the case, and not as 
part of the pattern.  We can of course do many things here, but I worry that 
the choice of operator affects peoples intuition about the semantics.  
(Historically keyword operators like instanceof have the weakest binding 
precedence.)  




Re: [External] : Re: Patterns and GADTs (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread Brian Goetz
The instanceof example is not a source backward compatible change, remember 
that instanceof is not a preview feature.

I’m well aware, can you give an example where flow typing of *type variables 
only* might lead to incompatibility?  (I’m aware that this is a possibility, 
but you’re stating it like its a fact already on the table.)  For example, 
where it would change overload selection (this is where flow typing for 
variables falls down, among other places.)

The main objection to that is that we do not have flow scoping for local 
variables but we have it for type variables which is weird.

We have inference for type variables when we don’t have them for types too.  
There are s many differences between type variables and ordinary type uses. 
 But yes, this would be flow typing for type variables.

I wonder if we can come with an explicit syntax for it, the same way instanceof 
String s is an explicit syntax for local variables.

By example, something like
 return switch (n) {
   case A n -> n.t;
   case B n -> n.s as T=String;
 };

but maybe it's too much ceremony.

Given the examples, I think this is something that stays better in the 
background.



Re: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread Brian Goetz
The questions we did not answer the last time we talk about that subject
- why should we allow raw types here ?

We already have a clear precedent with type patterns in instanceof — which is 
not a preview feature any more.  So for one, now you’re talking about making a 
*change* to the existing language semantics.  There are other concerns too.

- given that this is equivalent to an instanceof + cast, why we can not use 
diamond inference on cast ?

You’re not being clear about what you’re saying, you could be saying either of 
the following (or others):

 - You’re proposing diamond here, but not there, then your proposal is 
inconsistent, and therefore stupid.
 - I love your proposal, but I think we should additionally talk about other 
places to use diamond as well.

I can’t tell which of these you’re saying, or maybe its something else?

- how this inference work ? Is is the same inference than with the diamond 
constructor ?

Again, I can’t tell whether you’re saying “this is dumb, it can’t work”, or 
“this is great, but I can’t figure out the details.”




Re: Positioning of guards (was: Reviewing feedback on patterns in switch)

2022-01-26 Thread Remi Forax
- Original Message -
> From: "Tagir Valeev" 
> To: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, January 26, 2022 4:50:05 AM
> Subject: Re: Positioning of guards (was: Reviewing feedback on patterns in 
> switch)

> Hello!
> 
> For the record: I like the current version with &&. It's short and
> easy to understand (as people already know what && means in Java). I
> see no reason in replacing it with `when`, which is more limiting.
> 
>> because of the potential confusion should we ever choose to support switch 
>> over
>> boolean
> 
> It looks like any boolean expression that is a potentially constant
> differs syntactically from the guarded pattern, so we can distinguish
> between guarded pattern with && and boolean expression on AST level
> without resolving the references. End users will unlikely use anything
> other than explicit 'true' and 'false' constants, so it will add some
> complexity to the compiler but does not add any problems to real users
> 
>> because the && did not stand out enough as turning a total pattern into a
>> partial one
> 
> I think it's a matter of taste and habit. I, for one, already get used
> to it. It signals about partiality much more, compared to a simple
> type pattern. Looking at `CharSequence cs`, you cannot say whether
> it's total or not if you don't know the type of the selector
> expression. However, looking at `CharSequence cs && cs.length() > 0`
> you are completely sure it's not total. So if we need a clear signal
> to tell total and partial patterns apart, we have much bigger problems
> with type patterns.
> 
>> Guarded patterns are never total
> Except when guard is a constant expression that evaluates to `true`:
> 
> void test(Object obj) {
>switch (obj) { // compiles
>case Object s && true -> System.out.println(s);
>}
> }

I think we should separate the two ideas in Brian's mail,
one is should we allow a guard inside a pattern ? and the other is what is the 
syntax for a guard ?

My position is that we should only allow guard in a switch, not as pattern.

And i see no problem to use "&&" instead of "when", as Tagir, i'm kind of used 
to it too.

regards,
Rémi

> 
> On Wed, Jan 26, 2022 at 2:49 AM Brian Goetz  wrote:
>>
>> > 2.  Positioning of guards
>>
>> We received several forms of feedback over the form and placement of guarded
>> patterns.  Recall that we define a guarded pattern to be `P && g`, where P 
>> is a
>> pattern and g is a boolean expression.  Guarded patterns are never total.  
>> Note
>> that we had a choice of the guard being part of the pattern, or being part of
>> the `case` label; the current status chooses the former.  (Part of our
>> reasoning was that there might be other partial pattern contexts coming, and 
>> we
>> didn’t want to solve this problem each time. (For instanceof, it makes no
>> difference.) )
>>
>> I am prepared to reconsider the association of the guard with the pattern, 
>> and
>> instead treat it as part of the case.  This is expressively weaker but may 
>> have
>> other advantages.
>>
>> Additionally, people objected to the use of &&, not necessarily because
>> “keywords are better”, but because of the potential confusion should we ever
>> choose to support switch over boolean, and because the && did not stand out
>> enough as turning a total pattern into a partial one.  What the alternative
>> looks like is something like:
>>
>> switch (x) {
>> case Foo(var x, var y)
>> when x == y -> A;
>> case Foo(var x, var y) -> B;
>> }
>>
>> Here, `when` (bike shed to be painted separately) is a qualifier on the case,
>> not the pattern.  A total pattern with a `when` is considered a partial case.
>> This simplifies patterns, and moves the complexity of guards into switch,
>> where arguably it belongs.
>>
>> The loss of expressiveness is in not allowing nested patterns like:
>>
>> P(Q && guard)
>>
>> and instead having to move the guard to after the matching construct.  Some
>> users recoiled at seeing guards inside pattern invocations; it seemed to some
>> like mixing two things that should stay separate.  (For unrolling a nested
>> pattern, `case P(Q)` where Q is not total unrolls to `case Pvar alpha) when
> > alpha instanceof Q`.)


Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Remi Forax
- Original Message -
> From: "Tagir Valeev" 
> To: "Brian Goetz" 
> Cc: "amber-spec-experts" 
> Sent: Wednesday, January 26, 2022 5:20:24 AM
> Subject: Re: Treatment of total patterns (was: Reviewing feedback on patterns 
> in switch)

>> Null is only matched by a switch case that includes `case null`.  Switches 
>> with
>> no `case null` are treated as if they have a `case null: throw NPE`.  This
>> means that `case Object o` doesn’t match null; only `case null, Object o` 
>> does.
>> Total patterns are re-allowed in instanceof expressions, and are consistent 
>> with
>> their legacy form.
> 
> I strongly support this change.
> 
> In my experience, it's much more important to have automatic
> refactorings between switch and chains of 'if' than between nested and
> flat switches. People have chains of 'if's very often and they are not
> legacy. Sometimes, you want to add conditions unrelated to the
> selector expression, so it could be natural to convert 'switch' to
> 'if'. In other cases, you simplify the chain of 'if' statements and
> see that the new set of conditions nicely fits into a pattern switch.
> These if<->switch conversions will be an everyday tool for developers.
> In contrast, destructuring with a switch will be a comparatively rare
> thing, and it's even more rare when you need to convert nested
> switches to flat ones or vice versa. I'm saying this from my Kotlin
> programming experience where you can have when-is and sort of
> destructuring of data classes which are roughly similar to what we are
> doing for Java. One level 'when' is common, two-level 'when' or
> conditions on destructuring components are more rare.
> 
> We already implemented some kind of switch<->if conversion in IntelliJ
> IDEA. And it already has a number of corner cases to handle in order
> to support total patterns that match null. In particular, we cannot
> convert `case Object obj` to `if (x instanceof Object obj), as total
> patterns are prohibited for instanceof and null won't be matched
> anyway. We cannot just omit a condition, as `obj` could be used
> afterwards, so we have to explicitly declare a variable (and I
> believe, this part is still buggy and may produce incompilable code).
> The proposed change will make switch<->if refactorings more mechanical
> and predictable.
> 
> Another thing I mentioned before and want to stress again is that this
> change will allow us to infer required nullity for the variable used
> in the switch selector from AST only. No need to use resolution or
> type inference. This will make interprocedural analysis stronger.
> E.g., consider:
> // Test.java
> class Test {
>  static void test(A a) {
>switch(a) {
>case B b -> {}
>case C c -> {}
>case D d -> {}
>}
>  }
> }
> 
> There are two possibilities:
> 1. D is a superclass of A, thus the last pattern is total, and null is
> accepted here:
> 
> interface D {}
> interface A extends D {}
> interface B extends A {}
> interface C extends A {}
> 
> 2. A is a sealed type with B, C, and D inheritors, switch is
> exhaustive, and null is not accepted here:
> 
> sealed interface A {}
> non-sealed interface B extends A {}
> non-sealed interface C extends A {}
> non-sealed interface D extends A {}
> 
> So without looking at A definition (which might be anywhere) we cannot
> say whether test(null) will throw NPE or not. We cannot cache the
> knowledge about 'test' method parameter nullability within the
> Test.java, because its nullability might change if we change the
> hierarchy of A, even if Test.java content is untouched. Currently, we
> are conservative and not infer nullability when any unguarded pattern
> appears in switch cases. With the required `case null`, we can perform
> more precise analysis.


We should go a step further, this also means that with

switch(box) {
   case Box(B b) -> {}
   case Box(C c) -> {}
   case Box(D d) -> {}
   }

we have no idea if the switch will accept Box(null) or not.

So the idea that a type behave differently if nested inside a pattern or not is 
not a good one.

> 
> With best regards,
> Tagir Valeev.

regards,
Rémi

> 
> On Wed, Jan 26, 2022 at 2:47 AM Brian Goetz  wrote:
>>
>>
>> 1.  Treatment of total patterns in switch / instanceof
>>
>>
>> The handling of totality has been a long and painful discussion, trying to
>> balance between where we want this feature to land in the long term, and
>> people’s existing mental models of what switch and instanceof are supposed to
>> do.  Because we’ve made the conscious decision to rehabilitate switch rather
>> tha

Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Tagir Valeev
> Null is only matched by a switch case that includes `case null`.  Switches 
> with no `case null` are treated as if they have a `case null: throw NPE`.  
> This means that `case Object o` doesn’t match null; only `case null, Object 
> o` does.
> Total patterns are re-allowed in instanceof expressions, and are consistent 
> with their legacy form.

I strongly support this change.

In my experience, it's much more important to have automatic
refactorings between switch and chains of 'if' than between nested and
flat switches. People have chains of 'if's very often and they are not
legacy. Sometimes, you want to add conditions unrelated to the
selector expression, so it could be natural to convert 'switch' to
'if'. In other cases, you simplify the chain of 'if' statements and
see that the new set of conditions nicely fits into a pattern switch.
These if<->switch conversions will be an everyday tool for developers.
In contrast, destructuring with a switch will be a comparatively rare
thing, and it's even more rare when you need to convert nested
switches to flat ones or vice versa. I'm saying this from my Kotlin
programming experience where you can have when-is and sort of
destructuring of data classes which are roughly similar to what we are
doing for Java. One level 'when' is common, two-level 'when' or
conditions on destructuring components are more rare.

We already implemented some kind of switch<->if conversion in IntelliJ
IDEA. And it already has a number of corner cases to handle in order
to support total patterns that match null. In particular, we cannot
convert `case Object obj` to `if (x instanceof Object obj), as total
patterns are prohibited for instanceof and null won't be matched
anyway. We cannot just omit a condition, as `obj` could be used
afterwards, so we have to explicitly declare a variable (and I
believe, this part is still buggy and may produce incompilable code).
The proposed change will make switch<->if refactorings more mechanical
and predictable.

Another thing I mentioned before and want to stress again is that this
change will allow us to infer required nullity for the variable used
in the switch selector from AST only. No need to use resolution or
type inference. This will make interprocedural analysis stronger.
E.g., consider:
// Test.java
class Test {
  static void test(A a) {
switch(a) {
case B b -> {}
case C c -> {}
case D d -> {}
}
  }
}

There are two possibilities:
1. D is a superclass of A, thus the last pattern is total, and null is
accepted here:

interface D {}
interface A extends D {}
interface B extends A {}
interface C extends A {}

2. A is a sealed type with B, C, and D inheritors, switch is
exhaustive, and null is not accepted here:

sealed interface A {}
non-sealed interface B extends A {}
non-sealed interface C extends A {}
non-sealed interface D extends A {}

So without looking at A definition (which might be anywhere) we cannot
say whether test(null) will throw NPE or not. We cannot cache the
knowledge about 'test' method parameter nullability within the
Test.java, because its nullability might change if we change the
hierarchy of A, even if Test.java content is untouched. Currently, we
are conservative and not infer nullability when any unguarded pattern
appears in switch cases. With the required `case null`, we can perform
more precise analysis.

With best regards,
Tagir Valeev.

On Wed, Jan 26, 2022 at 2:47 AM Brian Goetz  wrote:
>
>
> 1.  Treatment of total patterns in switch / instanceof
>
>
> The handling of totality has been a long and painful discussion, trying to 
> balance between where we want this feature to land in the long term, and 
> people’s existing mental models of what switch and instanceof are supposed to 
> do.  Because we’ve made the conscious decision to rehabilitate switch rather 
> than make a new construct (which would live side by side with the old 
> construct forever), we have to take into account the preconceived mental 
> models to a greater degree.
>
> Totality is a predicate on a pattern and the static type of its match target; 
> for a pattern P to be total on T, it means that all values of T are matched 
> by P.  Note that when I say “matched by”, I am appealing not necessarily to 
> “what does switch do” or “what does instanceof do”, but to an abstract notion 
> of matching.
>
> The main place where there is a gap between pattern totality and whether a 
> pattern matches in a switch has to do with null.  We’ve done a nice job 
> retrofitting “case null” onto switch (especially with `case null, Foo f` 
> which allows the null to be bound to f), but people are still uncomfortable 
> with `case Object o` binding null to o.
>
> (Another place there is a gap is with nested patterns; Box(Bag(String s)) 
> should be total on Box>, but can’t match Box(null).   We don’t 
> want to force users to add default cases, but a switch on Box> 
> would need an implicit throwing case to deal with the remainder.)
>
> I 

Re: Positioning of guards (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Tagir Valeev
Hello!

For the record: I like the current version with &&. It's short and
easy to understand (as people already know what && means in Java). I
see no reason in replacing it with `when`, which is more limiting.

> because of the potential confusion should we ever choose to support switch 
> over boolean

It looks like any boolean expression that is a potentially constant
differs syntactically from the guarded pattern, so we can distinguish
between guarded pattern with && and boolean expression on AST level
without resolving the references. End users will unlikely use anything
other than explicit 'true' and 'false' constants, so it will add some
complexity to the compiler but does not add any problems to real users

> because the && did not stand out enough as turning a total pattern into a 
> partial one

I think it's a matter of taste and habit. I, for one, already get used
to it. It signals about partiality much more, compared to a simple
type pattern. Looking at `CharSequence cs`, you cannot say whether
it's total or not if you don't know the type of the selector
expression. However, looking at `CharSequence cs && cs.length() > 0`
you are completely sure it's not total. So if we need a clear signal
to tell total and partial patterns apart, we have much bigger problems
with type patterns.

> Guarded patterns are never total
Except when guard is a constant expression that evaluates to `true`:

void test(Object obj) {
switch (obj) { // compiles
case Object s && true -> System.out.println(s);
}
}

On Wed, Jan 26, 2022 at 2:49 AM Brian Goetz  wrote:
>
> > 2.  Positioning of guards
>
> We received several forms of feedback over the form and placement of guarded 
> patterns.  Recall that we define a guarded pattern to be `P && g`, where P is 
> a pattern and g is a boolean expression.  Guarded patterns are never total.  
> Note that we had a choice of the guard being part of the pattern, or being 
> part of the `case` label; the current status chooses the former.  (Part of 
> our reasoning was that there might be other partial pattern contexts coming, 
> and we didn’t want to solve this problem each time. (For intsanceof, it makes 
> no difference.) )
>
> I am prepared to reconsider the association of the guard with the pattern, 
> and instead treat it as part of the case.  This is expressively weaker but 
> may have other advantages.
>
> Additionally, people objected to the use of &&, not necessarily because 
> “keywords are better”, but because of the potential confusion should we ever 
> choose to support switch over boolean, and because the && did not stand out 
> enough as turning a total pattern into a partial one.  What the alternative 
> looks like is something like:
>
> switch (x) {
> case Foo(var x, var y)
> when x == y -> A;
> case Foo(var x, var y) -> B;
> }
>
> Here, `when` (bike shed to be painted separately) is a qualifier on the case, 
> not the pattern.  A total pattern with a `when` is considered a partial case. 
>  This simplifies patterns, and moves the complexity of guards into switch, 
> where arguably it belongs.
>
> The loss of expressiveness is in not allowing nested patterns like:
>
> P(Q && guard)
>
> and instead having to move the guard to after the matching construct.  Some 
> users recoiled at seeing guards inside pattern invocations; it seemed to some 
> like mixing two things that should stay separate.  (For unrolling a nested 
> pattern, `case P(Q)` where Q is not total unrolls to `case Pvar alpha) when 
> alpha instanceof Q`.)


Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Remi Forax
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, January 25, 2022 8:47:09 PM
> Subject: Treatment of total patterns (was: Reviewing feedback on patterns in
> switch)

>> 1. Treatment of total patterns in switch / instanceof

> The handling of totality has been a long and painful discussion, trying to
> balance between where we want this feature to land in the long term, and
> people’s existing mental models of what switch and instanceof are supposed to
> do. Because we’ve made the conscious decision to rehabilitate switch rather
> than make a new construct (which would live side by side with the old 
> construct
> forever), we have to take into account the preconceived mental models to a
> greater degree.

> Totality is a predicate on a pattern and the static type of its match target;
> for a pattern P to be total on T, it means that all values of T are matched by
> P. Note that when I say “matched by”, I am appealing not necessarily to “what
> does switch do” or “what does instanceof do”, but to an abstract notion of
> matching.

> The main place where there is a gap between pattern totality and whether a
> pattern matches in a switch has to do with null. We’ve done a nice job
> retrofitting “case null” onto switch (especially with `case null, Foo f` which
> allows the null to be bound to f), but people are still uncomfortable with
> `case Object o` binding null to o.

> (Another place there is a gap is with nested patterns; Box(Bag(String s)) 
> should
> be total on Box>, but can’t match Box(null). We don’t want to 
> force
> users to add default cases, but a switch on Box> would need an
> implicit throwing case to deal with the remainder.)

> I am not inclined to reopen the “should `Object o` be total” discussion; I
> really don’t think there’s anything new to say there. But we can refine the
> interaction between a total pattern and what the switch and instanceof
> constructs might do. Just because `Object o` is total on Object, doesn’t mean
> `case Object o` has to match it. It is the latter I am suggesting we might
> reopen.

> The motivation for treating total patterns as total (and therefore nullable) 
> in
> switch comes in part from the desire to avoid introducing sharp edges into
> refactoring. Specifically, we had two invariants in mind:

> x matches P(Q) === x matches P(var alpha) && alpha matches Q:

> and

> switch (x) {
> case P(Q): A
> case P(T): B
> }

> where T is total on the type of x, should be equivalent to

> switch (x) {
> case P(var alpha):
> switch(alpha) {
> case Q: A
> case T: B
> }
> }
> }

> These invariants are powerful both for linguistic transformation and for
> refactoring.

> The refinements I propose are:

> - Null is only matched by a switch case that includes `case null`. Switches 
> with
> no `case null` are treated as if they have a `case null: throw NPE`. This 
> means
> that `case Object o` doesn’t match null; only `case null, Object o` does.

> - Total patterns are re-allowed in instanceof expressions, and are consistent
> with their legacy form.

> Essentially, this gives switch and instanceof a chance to treat null specially
> with their existing semantics, which takes precedence over the pattern match.

> The consequences of this for our refactoring rules are:

> - When unrolling a nested pattern P(Q), we can only do so when Q is not total.
> - When unrolling a switch over nested patterns to a nested switch, `case P(T)`
> must be unrolled not to `case T`, but `case null, T`.

> These changes entail no changes to the semantics of pattern matching; they are
> changes to the semantics of instanceof/switch with regard to null.

I have a slight preference for the C# syntax, the only way to have a total 
pattern is to use "var" so case P(T) is equivalent to instanceof P p && p.t 
instanceof T t. 
Yes, it's not great because it means that "var" is not just inference but i 
think i prefer that compromise than having a type in a pattern means something 
different if it is nested or not. 

The semantics you are proposing (or the one currently implemented in Java 18) 
is objectively neither worst nor better than the C# one, it's just different. 
Pragmatically, we should choose the C# semantics, just because there are 
already thousands of people who knows it. 

Rémi 


Re: Positioning of guards (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Remi Forax
- Original Message -
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, January 25, 2022 8:49:02 PM
> Subject: Positioning of guards (was: Reviewing feedback on patterns in switch)

>> 2.  Positioning of guards
> 
> We received several forms of feedback over the form and placement of guarded
> patterns.  Recall that we define a guarded pattern to be `P && g`, where P is 
> a
> pattern and g is a boolean expression.  Guarded patterns are never total.  
> Note
> that we had a choice of the guard being part of the pattern, or being part of
> the `case` label; the current status chooses the former.  (Part of our
> reasoning was that there might be other partial pattern contexts coming, and 
> we
> didn’t want to solve this problem each time. (For intsanceof, it makes no
> difference.) )
> 
> I am prepared to reconsider the association of the guard with the pattern, and
> instead treat it as part of the case.  This is expressively weaker but may 
> have
> other advantages.
> 
> Additionally, people objected to the use of &&, not necessarily because
> “keywords are better”, but because of the potential confusion should we ever
> choose to support switch over boolean, and because the && did not stand out
> enough as turning a total pattern into a partial one.  What the alternative
> looks like is something like:
> 
>switch (x) {
>case Foo(var x, var y)
>when x == y -> A;
>case Foo(var x, var y) -> B;
>}
> 
> Here, `when` (bike shed to be painted separately) is a qualifier on the case,
> not the pattern.  A total pattern with a `when` is considered a partial case.
> This simplifies patterns, and moves the complexity of guards into switch,
> where arguably it belongs.
> 
> The loss of expressiveness is in not allowing nested patterns like:
> 
>P(Q && guard)
> 
> and instead having to move the guard to after the matching construct.  Some
> users recoiled at seeing guards inside pattern invocations; it seemed to some
> like mixing two things that should stay separate.  (For unrolling a nested
> pattern, `case P(Q)` where Q is not total unrolls to `case Pvar alpha) when
> alpha instanceof Q`.)

I think it's a good simplification.

Rémi


Re: Patterns and GADTs (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Remi Forax
- Original Message -
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, January 25, 2022 8:49:08 PM
> Subject: Patterns and GADTs (was: Reviewing feedback on patterns in switch)

>> 3.  Type refinements for GADTs
> 
> There are a number of unsatisfying aspects to how we currently handle GADTs;
> specifically, we are missing the type refinement process outlined in "Simple
> unification-based type inference for GADTs” (SPJ et al, 2005).  Here are some
> examples of where we fall down.
> 
> Suppose we have
> 
>sealed interface Node { }
>record A(T t) extends Node { }
>record B(String s) extends Node { }
> 
> and we want to write:
> 
> T unbox(Node n) {
>return switch (n) {
>case A n -> n.t;
>case B n -> n.s;
>};
>}
> 
> The RHS of all the arrows must be compatible with the return type, which is T.
> Clearly that is true for the first case, but not for the second; the compiler
> doesn’t know that T has to be String if we’ve matched a Node to B.  What is
> missing is to refine the type of T based on matches.  Here, we would gather an
> additional constraint on `case B` for T=String; if we had a case which was
> covariant in T:
> 
>record C(T t)
> 
> then a `case C` would gather an additional constraint of T <: B for its 
> RHS.
> 
> More generally, any code dominated by a match that provides additional bounds
> for type variables could benefit from those bounds.  For example, we’d 
> probably
> want the same in:
> 
>if (n instanceof B b) { /* T is String here */ }
> 
> and (as with flow scoping):
> 
>if (! (n instanceof B b))
>throw …
>// T is String here
> 
> We can probably piggy back the specification of this on flow scoping, 
> appealing
> to “wherever a binding introduced by this pattern would be in scope.”

I agree that we should do something to support GADTs

The instanceof example is not a source backward compatible change, remember 
that instanceof is not a preview feature.

The main objection to that is that we do not have flow scoping for local 
variables but we have it for type variables which is weird.
I wonder if we can come with an explicit syntax for it, the same way instanceof 
String s is an explicit syntax for local variables.

By example, something like
  return switch (n) {
case A n -> n.t;
case B n -> n.s as T=String;
  };

but maybe it's too much ceremony.

regards,
Rémi


Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Remi Forax
- Original Message -
> From: "Brian Goetz" 
> To: "amber-spec-experts" 
> Sent: Tuesday, January 25, 2022 8:49:12 PM
> Subject: Diamond in type patterns (was: Reviewing feedback on patterns in 
> switch)

>> 4.  Diamond for type patterns (and record patterns)
> 
> 
> The type pattern `T t` declares `t`, if the pattern matches, with the type T.
> If T is a generic type, then we do a consistency check to ensure soundness:
> 
>List list = …
>switch (list) {
>case ArrayList a: A  // ok
>case ArrayList a: B   // ok
>case ArrayList a: C// ok, raw type
>case ArrayList a:  // error, would require unchecked conversion
>   }
> 
> All of these make sense, but users are going to be tempted to use `case
> ArrayList a` rather than the full `case ArrayList a`, and then be sad
> (or confused) when they get type errors.  Since the type can be precisely
> defined by inference, this seems a place for allowing diamond:
> 
>case ArrayList<> a: B
> 
> (And the same when we have record patterns.)

The questions we did not answer the last time we talk about that subject
 - why should we allow raw types here ?
 - given that this is equivalent to an instanceof + cast, why we can not use 
diamond inference on cast ?
 - how this inference work ? Is is the same inference than with the diamond 
constructor ?
   By example, if we have

   List list = ...
   switch(list) {
 case ArrayList<> a:
   }

   Do we really want to infer ArrayList to then rejects it because it's 
an unsafe cast.

regards,
Rémi


Diamond in type patterns (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Brian Goetz
> 4.  Diamond for type patterns (and record patterns)


The type pattern `T t` declares `t`, if the pattern matches, with the type T.  
If T is a generic type, then we do a consistency check to ensure soundness:

List list = …
switch (list) { 
case ArrayList a: A  // ok
case ArrayList a: B   // ok
case ArrayList a: C// ok, raw type
case ArrayList a:  // error, would require unchecked conversion 
   }

All of these make sense, but users are going to be tempted to use `case 
AerrayList a` rather than the full `case ArrayList a`, and then be sad 
(or confused) when they get type errors.  Since the type can be precisely 
defined by inference, this seems a place for allowing diamond:

case ArrayList<> a: B

(And the same when we have record patterns.)  



Patterns and GADTs (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Brian Goetz
> 3.  Type refinements for GADTs

There are a number of unsatisfying aspects to how we currently handle GADTs; 
specifically, we are missing the type refinement process outlined in "Simple 
unification-based type inference for GADTs” (SPJ et al, 2005).  Here are some 
examples of where we fall down.  

Suppose we have

sealed interface Node { }
record A(T t) extends Node { }
record B(String s) extends Node { } 

and we want to write:

 T unbox(Node n) { 
return switch (n) { 
case A n -> n.t;
case B n -> n.s;
}
}

The RHS of all the arrows must be compatible with the return type, which is T.  
Clearly that is true for the first case, but not for the second; the compiler 
doesn’t know that T has to be String if we’ve matched a Node to B.  What is 
missing is to refine the type of T based on matches.  Here, we would gather an 
additional constraint on `case B` for T=String; if we had a case which was 
covariant in T:

record C(T t)

then a `case C` would gather an additional constraint of T <: B for its RHS. 
 

More generally, any code dominated by a match that provides additional bounds 
for type variables could benefit from those bounds.  For example, we’d probably 
want the same in:

if (n instanceof B b) { /* T is String here */ }

and (as with flow scoping): 

if (! (n instanceof B b))
throw …
// T is String here

We can probably piggy back the specification of this on flow scoping, appealing 
to “wherever a binding introduced by this pattern would be in scope.”  



Positioning of guards (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Brian Goetz
> 2.  Positioning of guards

We received several forms of feedback over the form and placement of guarded 
patterns.  Recall that we define a guarded pattern to be `P && g`, where P is a 
pattern and g is a boolean expression.  Guarded patterns are never total.  Note 
that we had a choice of the guard being part of the pattern, or being part of 
the `case` label; the current status chooses the former.  (Part of our 
reasoning was that there might be other partial pattern contexts coming, and we 
didn’t want to solve this problem each time. (For intsanceof, it makes no 
difference.) )

I am prepared to reconsider the association of the guard with the pattern, and 
instead treat it as part of the case.  This is expressively weaker but may have 
other advantages.  

Additionally, people objected to the use of &&, not necessarily because 
“keywords are better”, but because of the potential confusion should we ever 
choose to support switch over boolean, and because the && did not stand out 
enough as turning a total pattern into a partial one.  What the alternative 
looks like is something like:

switch (x) { 
case Foo(var x, var y)
when x == y -> A;
case Foo(var x, var y) -> B;
}

Here, `when` (bike shed to be painted separately) is a qualifier on the case, 
not the pattern.  A total pattern with a `when` is considered a partial case.  
This simplifies patterns, and moves the complexity of guards into switch, where 
arguably it belongs.  

The loss of expressiveness is in not allowing nested patterns like:

P(Q && guard)

and instead having to move the guard to after the matching construct.  Some 
users recoiled at seeing guards inside pattern invocations; it seemed to some 
like mixing two things that should stay separate.  (For unrolling a nested 
pattern, `case P(Q)` where Q is not total unrolls to `case Pvar alpha) when 
alpha instanceof Q`.)  

Treatment of total patterns (was: Reviewing feedback on patterns in switch)

2022-01-25 Thread Brian Goetz

1.  Treatment of total patterns in switch / instanceof

The handling of totality has been a long and painful discussion, trying to 
balance between where we want this feature to land in the long term, and 
people’s existing mental models of what switch and instanceof are supposed to 
do.  Because we’ve made the conscious decision to rehabilitate switch rather 
than make a new construct (which would live side by side with the old construct 
forever), we have to take into account the preconceived mental models to a 
greater degree.

Totality is a predicate on a pattern and the static type of its match target; 
for a pattern P to be total on T, it means that all values of T are matched by 
P.  Note that when I say “matched by”, I am appealing not necessarily to “what 
does switch do” or “what does instanceof do”, but to an abstract notion of 
matching.

The main place where there is a gap between pattern totality and whether a 
pattern matches in a switch has to do with null.  We’ve done a nice job 
retrofitting “case null” onto switch (especially with `case null, Foo f` which 
allows the null to be bound to f), but people are still uncomfortable with 
`case Object o` binding null to o.

(Another place there is a gap is with nested patterns; Box(Bag(String s)) 
should be total on Box>, but can’t match Box(null).   We don’t want 
to force users to add default cases, but a switch on Box> would 
need an implicit throwing case to deal with the remainder.)

I am not inclined to reopen the “should `Object o` be total” discussion; I 
really don’t think there’s anything new to say there.  But we can refine the 
interaction between a total pattern and what the switch and instanceof 
constructs might do.  Just because `Object o` is total on Object, doesn’t mean 
`case Object o` has to match it.  It is the latter I am suggesting we might 
reopen.

The motivation for treating total patterns as total (and therefore nullable) in 
switch comes in part from the desire to avoid introducing sharp edges into 
refactoring.  Specifically, we had two invariants in mind:

x matches P(Q) === x matches P(var alpha) && alpha matches Q:

and

switch (x) {
case P(Q): A
case P(T): B
}

where T is total on the type of x, should be equivalent to

switch (x) {
case P(var alpha):
switch(alpha) {
case Q: A
case T: B
}
}
   }

These invariants are powerful both for linguistic transformation and for 
refactoring.

The refinements I propose are:

 - Null is only matched by a switch case that includes `case null`.  Switches 
with no `case null` are treated as if they have a `case null: throw NPE`.  This 
means that `case Object o` doesn’t match null; only `case null, Object o` does.

 - Total patterns are re-allowed in instanceof expressions, and are consistent 
with their legacy form.

Essentially, this gives switch and instanceof a chance to treat null specially 
with their existing semantics, which takes precedence over the pattern match.

The consequences of this for our refactoring rules are:

 - When unrolling a nested pattern P(Q), we can only do so when Q is not total.
 - When unrolling a switch over nested patterns to a nested switch, `case P(T)` 
must be unrolled not to `case T`, but `case null, T`.


These changes entail no changes to the semantics of pattern matching; they are 
changes to the semantics of instanceof/switch with regard to null.



Reviewing feedback on patterns in switch

2022-01-25 Thread Brian Goetz
We’ve previewed patterns in switch for two rounds, and have received some 
feedback.  Overall, things work quite well, but there were a few items which 
received some nontrivial feedback, and I’m prepared to suggest some changes 
based on them.  I’ll summarize them here and create a new thread for each with 
a more detailed description.  

I’ll make a call for additional items a little later; for now, let’s focus on 
these items before adding new things (or reopening old ones.)  

1.  Treatment of total patterns in switch / instanceof

2.  Positioning of guards

3.  Type refinements for GADTs

4.  Diamond for type patterns (and record patterns)