From mark at io7m.com Tue Jan 4 13:10:32 2022 From: mark at io7m.com (Mark Raynsford) Date: Tue, 4 Jan 2022 13:10:32 +0000 Subject: Sealed interfaces with hidden implementation superclasses Message-ID: <20220104131032.53353f9f@sunflower.int.arc7.info> (Apologies for the possible double post: I tried to post this to amber-dev@ and the message apparently never made it there. I asked amber-dev-owner@ but got no response there either). I've run across some behaviour I didn't anticipate with regards to sealed interfaces. I suspect that it's expected behaviour, but I feel like maybe the type system is overly constraining in this particular case. Imagine a small UI library with a sealed set of primitive components: sealed interface Component permits Button, TextView, ImageView {} non-sealed interface Button extends Component {} non-sealed interface TextView extends Component {} non-sealed interface ImageView extends Component {} The set of primitive components is sealed because doing so simplifies other parts of the system; more complex components are just aggregates of the primitives. Then, in one or more separate modules, we have some platform-specific implementation classes: final class X11Button implements Button final class X11TextView implements TextView final class X11ImageView implements ImageView final class QNXButton implements Button final class QNXTextView implements TextView final class QNXImageView implements ImageView This is all fine so far. The problem occurs when one of those implementations wants to introduce a private abstract superclass that contains shared implementation code but that - importantly - isn't actually intended to ever be observed on its own outside of the module. Naturally, that superclass wants to implement Component so that it can provide implementations of the common methods: abstract class QNXComponent implements Component final class QNXButton extends QNXComponent implements Button final class QNXTextView extends QNXComponent implements TextView final class QNXImageView extends QNXComponent implements ImageView The compiler (rightfully) complains that QNXComponent isn't in the "permits" list of Component. We can obviously remove the "implements Component" from QNXComponent, but this then means adding boilerplate @Override methods in each of QNXButton, QNXTextView, etc, that call the now-non- at Override methods in the abstract QNXComponent class. -- Mark Raynsford | https://www.io7m.com From vicente.romero at oracle.com Fri Jan 21 17:26:45 2022 From: vicente.romero at oracle.com (Vicente Romero) Date: Fri, 21 Jan 2022 12:26:45 -0500 Subject: Sealed interfaces with hidden implementation superclasses In-Reply-To: <20220104131032.53353f9f@sunflower.int.arc7.info> References: <20220104131032.53353f9f@sunflower.int.arc7.info> Message-ID: <48b43467-463b-af93-d4fa-d73196fa213e@oracle.com> Hi Mark, On 1/4/22 08:10, Mark Raynsford wrote: > (Apologies for the possible double post: I tried to post this to > amber-dev@ and the message apparently never made it there. I asked > amber-dev-owner@ but got no response there either). > > I've run across some behaviour I didn't anticipate with regards to > sealed interfaces. I suspect that it's expected behaviour, right this is expected behavior, if a sealed super class or super interface clearly defines its list of permitted subtypes, it doesn't make sense to define some corner case rules for which this can be violated depending on the accessibility of the subtypes. It could be that your hierarchy is not a perfect candidate to be converted to a sealed hierarchy or it could be that you need to cut some corners for it to fit into the sealed box. Just my 0.02 Thanks, Vicente > but I > feel like maybe the type system is overly constraining in this > particular case. > > Imagine a small UI library with a sealed set of primitive components: > > sealed interface Component permits Button, TextView, ImageView {} > non-sealed interface Button extends Component {} > non-sealed interface TextView extends Component {} > non-sealed interface ImageView extends Component {} > > The set of primitive components is sealed because doing so simplifies > other parts of the system; more complex components are just aggregates > of the primitives. > > Then, in one or more separate modules, we have some platform-specific > implementation classes: > > final class X11Button implements Button > final class X11TextView implements TextView > final class X11ImageView implements ImageView > > final class QNXButton implements Button > final class QNXTextView implements TextView > final class QNXImageView implements ImageView > > This is all fine so far. The problem occurs when one of those > implementations wants to introduce a private abstract superclass > that contains shared implementation code but that - importantly - isn't > actually intended to ever be observed on its own outside of the module. > > Naturally, that superclass wants to implement Component so that it can > provide implementations of the common methods: > > abstract class QNXComponent implements Component > final class QNXButton extends QNXComponent implements Button > final class QNXTextView extends QNXComponent implements TextView > final class QNXImageView extends QNXComponent implements ImageView > > The compiler (rightfully) complains that QNXComponent isn't in > the "permits" list of Component. We can obviously remove > the "implements Component" from QNXComponent, but this then > means adding boilerplate @Override methods in each of QNXButton, > QNXTextView, etc, that call the now-non- at Override methods in > the abstract QNXComponent class. > From brian.goetz at oracle.com Tue Jan 25 19:46:09 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 25 Jan 2022 19:46:09 +0000 Subject: Reviewing feedback on patterns in switch Message-ID: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> 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) From brian.goetz at oracle.com Tue Jan 25 19:47:09 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 25 Jan 2022 19:47:09 +0000 Subject: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> Message-ID: <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> 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. -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Tue Jan 25 19:49:02 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 25 Jan 2022 19:49:02 +0000 Subject: Positioning of guards (was: Reviewing feedback on patterns in switch) In-Reply-To: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> Message-ID: <562B7F7D-D09C-4C3E-B0C7-0215BF64601B@oracle.com> > 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`.) From brian.goetz at oracle.com Tue Jan 25 19:49:08 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 25 Jan 2022 19:49:08 +0000 Subject: Patterns and GADTs (was: Reviewing feedback on patterns in switch) In-Reply-To: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> Message-ID: <1637C5D4-C052-4826-888B-C27904E84C1F@oracle.com> > 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.? From brian.goetz at oracle.com Tue Jan 25 19:49:12 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 25 Jan 2022 19:49:12 +0000 Subject: Diamond in type patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> Message-ID: <8DF34BA2-8A92-45D4-9F1A-62B0096D842F@oracle.com> > 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.) From forax at univ-mlv.fr Tue Jan 25 22:01:11 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 25 Jan 2022 23:01:11 +0100 (CET) Subject: Diamond in type patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <8DF34BA2-8A92-45D4-9F1A-62B0096D842F@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8DF34BA2-8A92-45D4-9F1A-62B0096D842F@oracle.com> Message-ID: <1180680225.4253980.1643148071510.JavaMail.zimbra@u-pem.fr> ----- 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 From forax at univ-mlv.fr Tue Jan 25 22:22:08 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 25 Jan 2022 23:22:08 +0100 (CET) Subject: Patterns and GADTs (was: Reviewing feedback on patterns in switch) In-Reply-To: <1637C5D4-C052-4826-888B-C27904E84C1F@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <1637C5D4-C052-4826-888B-C27904E84C1F@oracle.com> Message-ID: <879580557.4260140.1643149328650.JavaMail.zimbra@u-pem.fr> ----- 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 From forax at univ-mlv.fr Tue Jan 25 22:34:18 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 25 Jan 2022 23:34:18 +0100 (CET) Subject: Positioning of guards (was: Reviewing feedback on patterns in switch) In-Reply-To: <562B7F7D-D09C-4C3E-B0C7-0215BF64601B@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <562B7F7D-D09C-4C3E-B0C7-0215BF64601B@oracle.com> Message-ID: <892386195.4262352.1643150058066.JavaMail.zimbra@u-pem.fr> ----- 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 From forax at univ-mlv.fr Tue Jan 25 23:03:53 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 26 Jan 2022 00:03:53 +0100 (CET) Subject: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> Message-ID: <2127411362.4267701.1643151833502.JavaMail.zimbra@u-pem.fr> > 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 -------------- next part -------------- An HTML attachment was scrubbed... URL: From amaembo at gmail.com Wed Jan 26 03:50:05 2022 From: amaembo at gmail.com (Tagir Valeev) Date: Wed, 26 Jan 2022 10:50:05 +0700 Subject: Positioning of guards (was: Reviewing feedback on patterns in switch) In-Reply-To: <562B7F7D-D09C-4C3E-B0C7-0215BF64601B@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <562B7F7D-D09C-4C3E-B0C7-0215BF64601B@oracle.com> Message-ID: 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`.) From amaembo at gmail.com Wed Jan 26 04:20:24 2022 From: amaembo at gmail.com (Tagir Valeev) Date: Wed, 26 Jan 2022 11:20:24 +0700 Subject: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> Message-ID: > 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 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. > From forax at univ-mlv.fr Wed Jan 26 07:53:37 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 26 Jan 2022 08:53:37 +0100 (CET) Subject: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> Message-ID: <1194660059.4382759.1643183617456.JavaMail.zimbra@u-pem.fr> ----- 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 >> 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. From forax at univ-mlv.fr Wed Jan 26 07:59:39 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 26 Jan 2022 08:59:39 +0100 (CET) Subject: Positioning of guards (was: Reviewing feedback on patterns in switch) In-Reply-To: References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <562B7F7D-D09C-4C3E-B0C7-0215BF64601B@oracle.com> Message-ID: <734414769.4390021.1643183979082.JavaMail.zimbra@u-pem.fr> ----- 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`.) From brian.goetz at oracle.com Wed Jan 26 12:23:04 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 26 Jan 2022 12:23:04 +0000 Subject: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <1180680225.4253980.1643148071510.JavaMail.zimbra@u-pem.fr> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8DF34BA2-8A92-45D4-9F1A-62B0096D842F@oracle.com> <1180680225.4253980.1643148071510.JavaMail.zimbra@u-pem.fr> Message-ID: <4B5A0A16-D6F1-4D94-9C13-E46BF546DAC6@oracle.com> 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.? -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Jan 26 12:28:19 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 26 Jan 2022 12:28:19 +0000 Subject: [External] : Re: Patterns and GADTs (was: Reviewing feedback on patterns in switch) In-Reply-To: <879580557.4260140.1643149328650.JavaMail.zimbra@u-pem.fr> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <1637C5D4-C052-4826-888B-C27904E84C1F@oracle.com> <879580557.4260140.1643149328650.JavaMail.zimbra@u-pem.fr> Message-ID: <86EE1C38-EF17-4F2A-98E2-A4A47DA252F6@oracle.com> 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 soooo 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. -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Jan 26 12:35:47 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 26 Jan 2022 12:35:47 +0000 Subject: [External] : Re: Positioning of guards (was: Reviewing feedback on patterns in switch) In-Reply-To: References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <562B7F7D-D09C-4C3E-B0C7-0215BF64601B@oracle.com> Message-ID: <218A69D3-AB30-459F-B322-F74F11F259AF@oracle.com> > 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.) From brian.goetz at oracle.com Wed Jan 26 12:40:05 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 26 Jan 2022 12:40:05 +0000 Subject: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> Message-ID: <9CAFC09D-A5AF-4004-AD58-0D69D4765010@oracle.com> > 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? From brian.goetz at oracle.com Wed Jan 26 12:47:38 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 26 Jan 2022 12:47:38 +0000 Subject: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <1194660059.4382759.1643183617456.JavaMail.zimbra@u-pem.fr> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> <1194660059.4382759.1643183617456.JavaMail.zimbra@u-pem.fr> Message-ID: <81EFA716-D027-4EE2-A269-5682FB718F86@oracle.com> 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 > 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. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Wed Jan 26 13:45:10 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 26 Jan 2022 14:45:10 +0100 (CET) Subject: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <81EFA716-D027-4EE2-A269-5682FB718F86@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> <1194660059.4382759.1643183617456.JavaMail.zimbra@u-pem.fr> <81EFA716-D027-4EE2-A269-5682FB718F86@oracle.com> Message-ID: <853217783.4728786.1643204710821.JavaMail.zimbra@u-pem.fr> > 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:forax at univ-mlv.fr | >> forax at 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. -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Jan 26 14:08:39 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 26 Jan 2022 14:08:39 +0000 Subject: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <853217783.4728786.1643204710821.JavaMail.zimbra@u-pem.fr> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> <1194660059.4382759.1643183617456.JavaMail.zimbra@u-pem.fr> <81EFA716-D027-4EE2-A269-5682FB718F86@oracle.com> <853217783.4728786.1643204710821.JavaMail.zimbra@u-pem.fr> Message-ID: 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, forax at univ-mlv.fr wrote: ________________________________ 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 > 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. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Wed Jan 26 14:16:35 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 26 Jan 2022 15:16:35 +0100 (CET) Subject: [External] : Re: Patterns and GADTs (was: Reviewing feedback on patterns in switch) In-Reply-To: <86EE1C38-EF17-4F2A-98E2-A4A47DA252F6@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <1637C5D4-C052-4826-888B-C27904E84C1F@oracle.com> <879580557.4260140.1643149328650.JavaMail.zimbra@u-pem.fr> <86EE1C38-EF17-4F2A-98E2-A4A47DA252F6@oracle.com> Message-ID: <104434748.4765465.1643206595722.JavaMail.zimbra@u-pem.fr> > 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 -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Wed Jan 26 14:29:34 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 26 Jan 2022 15:29:34 +0100 (CET) Subject: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <4B5A0A16-D6F1-4D94-9C13-E46BF546DAC6@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8DF34BA2-8A92-45D4-9F1A-62B0096D842F@oracle.com> <1180680225.4253980.1643148071510.JavaMail.zimbra@u-pem.fr> <4B5A0A16-D6F1-4D94-9C13-E46BF546DAC6@oracle.com> Message-ID: <597457348.4775102.1643207374235.JavaMail.zimbra@u-pem.fr> > 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 -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Jan 26 14:30:45 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 26 Jan 2022 14:30:45 +0000 Subject: [External] : Re: Patterns and GADTs (was: Reviewing feedback on patterns in switch) In-Reply-To: <104434748.4765465.1643206595722.JavaMail.zimbra@u-pem.fr> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <1637C5D4-C052-4826-888B-C27904E84C1F@oracle.com> <879580557.4260140.1643149328650.JavaMail.zimbra@u-pem.fr> <86EE1C38-EF17-4F2A-98E2-A4A47DA252F6@oracle.com> <104434748.4765465.1643206595722.JavaMail.zimbra@u-pem.fr> Message-ID: 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, forax at univ-mlv.fr wrote: ________________________________ 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 -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Jan 26 14:34:21 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 26 Jan 2022 14:34:21 +0000 Subject: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <597457348.4775102.1643207374235.JavaMail.zimbra@u-pem.fr> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8DF34BA2-8A92-45D4-9F1A-62B0096D842F@oracle.com> <1180680225.4253980.1643148071510.JavaMail.zimbra@u-pem.fr> <4B5A0A16-D6F1-4D94-9C13-E46BF546DAC6@oracle.com> <597457348.4775102.1643207374235.JavaMail.zimbra@u-pem.fr> Message-ID: 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? -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Jan 26 15:42:23 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 26 Jan 2022 15:42:23 +0000 Subject: Reviewing feedback on patterns in switch In-Reply-To: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> Message-ID: <31A4388B-FD96-40F8-A1EA-726FB324C3F8@oracle.com> 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. From forax at univ-mlv.fr Thu Jan 27 07:57:12 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 27 Jan 2022 08:57:12 +0100 (CET) Subject: Feedback: refining the inference of yield type in a switch Message-ID: <856304100.5107247.1643270232184.JavaMail.zimbra@u-pem.fr> Here are several examples showing that the inference does work as expected from a beginner POV, the first two examples come from one of my student. Here, list is typed as List instead of List var value = ... var list = switch (value) { case 0 -> List.of(); case 1 -> List.of("foo"); default -> throw new AssertionError(); }; if we add a cast in front of to try to help inference var list = (List) switch (value) { case 0 -> List.of(); case 1 -> List.of("foo"); default -> throw new AssertionError(); }; the inference does not care, so it becomes an unsafe cast. Declaring the type explicitly works List list = switch (value) { case 0 -> List.of(); case 1 -> List.of("foo"); default -> throw new AssertionError(); }; Specifying the type argument for List.of() also works. var list = switch (value) { case 0 -> List.of(); case 1 -> List.of("foo"); default -> throw new AssertionError(); }; To summarize, the inference considers each yield type one by one instead of all together (each one adding it's own constraints). I think we should refine the way the inference work so "it just works" in this kind of case. regards, R?mi From forax at univ-mlv.fr Thu Jan 27 12:42:24 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 27 Jan 2022 13:42:24 +0100 (CET) Subject: [External] : Re: Patterns and GADTs (was: Reviewing feedback on patterns in switch) In-Reply-To: References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <1637C5D4-C052-4826-888B-C27904E84C1F@oracle.com> <879580557.4260140.1643149328650.JavaMail.zimbra@u-pem.fr> <86EE1C38-EF17-4F2A-98E2-A4A47DA252F6@oracle.com> <104434748.4765465.1643206595722.JavaMail.zimbra@u-pem.fr> Message-ID: <217927803.5341488.1643287344395.JavaMail.zimbra@u-pem.fr> > 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:forax at univ-mlv.fr | >> forax at univ-mlv.fr ] wrote: >>> From: "Brian Goetz" < [ mailto:brian.goetz at oracle.com | brian.goetz at oracle.com ] >>> > >>> To: "Remi Forax" < [ mailto:forax at univ-mlv.fr | forax at univ-mlv.fr ] > >>> Cc: "amber-spec-experts" < [ mailto:amber-spec-experts at openjdk.java.net | >>> amber-spec-experts at 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 -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Jan 27 12:43:55 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 27 Jan 2022 13:43:55 +0100 (CET) Subject: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> <1194660059.4382759.1643183617456.JavaMail.zimbra@u-pem.fr> <81EFA716-D027-4EE2-A269-5682FB718F86@oracle.com> <853217783.4728786.1643204710821.JavaMail.zimbra@u-pem.fr> Message-ID: <1502085498.5343051.1643287435210.JavaMail.zimbra@u-pem.fr> > 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:forax at univ-mlv.fr | >> forax at univ-mlv.fr ] wrote: >>> From: "Brian Goetz" < [ mailto:brian.goetz at oracle.com | brian.goetz at oracle.com ] >>> > >>> To: "Remi Forax" < [ mailto:forax at univ-mlv.fr | forax at univ-mlv.fr ] > >>> Cc: "Tagir Valeev" < [ mailto:amaembo at gmail.com | amaembo at gmail.com ] >, >>> "amber-spec-experts" < [ mailto:amber-spec-experts at openjdk.java.net | >>> amber-spec-experts at 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:forax at univ-mlv.fr | >>>> forax at 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. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Jan 27 12:56:48 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 27 Jan 2022 13:56:48 +0100 (CET) Subject: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8DF34BA2-8A92-45D4-9F1A-62B0096D842F@oracle.com> <1180680225.4253980.1643148071510.JavaMail.zimbra@u-pem.fr> <4B5A0A16-D6F1-4D94-9C13-E46BF546DAC6@oracle.com> <597457348.4775102.1643207374235.JavaMail.zimbra@u-pem.fr> Message-ID: <2027560118.5362183.1643288208677.JavaMail.zimbra@u-pem.fr> > 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 -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Jan 27 13:04:35 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 27 Jan 2022 13:04:35 +0000 Subject: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <2027560118.5362183.1643288208677.JavaMail.zimbra@u-pem.fr> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8DF34BA2-8A92-45D4-9F1A-62B0096D842F@oracle.com> <1180680225.4253980.1643148071510.JavaMail.zimbra@u-pem.fr> <4B5A0A16-D6F1-4D94-9C13-E46BF546DAC6@oracle.com> <597457348.4775102.1643207374235.JavaMail.zimbra@u-pem.fr> <2027560118.5362183.1643288208677.JavaMail.zimbra@u-pem.fr> Message-ID: 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? -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Jan 27 13:20:58 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 27 Jan 2022 14:20:58 +0100 (CET) Subject: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8DF34BA2-8A92-45D4-9F1A-62B0096D842F@oracle.com> <1180680225.4253980.1643148071510.JavaMail.zimbra@u-pem.fr> <4B5A0A16-D6F1-4D94-9C13-E46BF546DAC6@oracle.com> <597457348.4775102.1643207374235.JavaMail.zimbra@u-pem.fr> <2027560118.5362183.1643288208677.JavaMail.zimbra@u-pem.fr> Message-ID: <1182891188.5383206.1643289658436.JavaMail.zimbra@u-pem.fr> > 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 -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Jan 27 14:27:57 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 27 Jan 2022 14:27:57 +0000 Subject: [External] : Re: Diamond in type patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <1182891188.5383206.1643289658436.JavaMail.zimbra@u-pem.fr> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8DF34BA2-8A92-45D4-9F1A-62B0096D842F@oracle.com> <1180680225.4253980.1643148071510.JavaMail.zimbra@u-pem.fr> <4B5A0A16-D6F1-4D94-9C13-E46BF546DAC6@oracle.com> <597457348.4775102.1643207374235.JavaMail.zimbra@u-pem.fr> <2027560118.5362183.1643288208677.JavaMail.zimbra@u-pem.fr> <1182891188.5383206.1643289658436.JavaMail.zimbra@u-pem.fr> Message-ID: <68E5F118-1DCB-48F0-8461-3DFD1A80B57A@oracle.com> 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, forax at univ-mlv.fr wrote: ________________________________ 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 -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Jan 27 15:41:27 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 27 Jan 2022 15:41:27 +0000 Subject: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <1502085498.5343051.1643287435210.JavaMail.zimbra@u-pem.fr> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <8B8F0C8E-45AF-48D0-ACB1-25675A898114@oracle.com> <1194660059.4382759.1643183617456.JavaMail.zimbra@u-pem.fr> <81EFA716-D027-4EE2-A269-5682FB718F86@oracle.com> <853217783.4728786.1643204710821.JavaMail.zimbra@u-pem.fr> <1502085498.5343051.1643287435210.JavaMail.zimbra@u-pem.fr> Message-ID: <3825AF52-4EB4-4188-87FF-79CEE9AC1719@oracle.com> 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. -------------- next part -------------- An HTML attachment was scrubbed... URL: From mark at io7m.com Fri Jan 28 11:43:55 2022 From: mark at io7m.com (Mark Raynsford) Date: Fri, 28 Jan 2022 11:43:55 +0000 Subject: Reviewing feedback on patterns in switch In-Reply-To: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> Message-ID: <20220128114355.754d9166@sunflower.int.arc7.info> On 2022-01-25T19:46:09 +0000 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 From forax at univ-mlv.fr Fri Jan 28 12:40:49 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 28 Jan 2022 13:40:49 +0100 (CET) Subject: Reviewing feedback on patterns in switch In-Reply-To: <20220128114355.754d9166@sunflower.int.arc7.info> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <20220128114355.754d9166@sunflower.int.arc7.info> Message-ID: <215980014.6024127.1643373649444.JavaMail.zimbra@u-pem.fr> ----- 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 +0000 > 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 From forax at univ-mlv.fr Fri Jan 28 13:03:20 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Fri, 28 Jan 2022 14:03:20 +0100 (CET) Subject: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <3825AF52-4EB4-4188-87FF-79CEE9AC1719@oracle.com> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <1194660059.4382759.1643183617456.JavaMail.zimbra@u-pem.fr> <81EFA716-D027-4EE2-A269-5682FB718F86@oracle.com> <853217783.4728786.1643204710821.JavaMail.zimbra@u-pem.fr> <1502085498.5343051.1643287435210.JavaMail.zimbra@u-pem.fr> <3825AF52-4EB4-4188-87FF-79CEE9AC1719@oracle.com> Message-ID: <1596392940.6044225.1643375000184.JavaMail.zimbra@u-pem.fr> > 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). -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Jan 28 18:44:21 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 28 Jan 2022 13:44:21 -0500 Subject: [External] : Re: Treatment of total patterns (was: Reviewing feedback on patterns in switch) In-Reply-To: <1596392940.6044225.1643375000184.JavaMail.zimbra@u-pem.fr> References: <06062579-A7DD-499C-839D-B77A319E38ED@oracle.com> <1194660059.4382759.1643183617456.JavaMail.zimbra@u-pem.fr> <81EFA716-D027-4EE2-A269-5682FB718F86@oracle.com> <853217783.4728786.1643204710821.JavaMail.zimbra@u-pem.fr> <1502085498.5343051.1643287435210.JavaMail.zimbra@u-pem.fr> <3825AF52-4EB4-4188-87FF-79CEE9AC1719@oracle.com> <1596392940.6044225.1643375000184.JavaMail.zimbra@u-pem.fr> Message-ID: <1c0a30bc-40a2-4f07-9462-03ee0952c91f@oracle.com> > 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. -------------- next part -------------- An HTML attachment was scrubbed... URL: