From forax at univ-mlv.fr Fri Sep 3 13:41:52 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 3 Sep 2021 15:41:52 +0200 (CEST) Subject: JEP 406 Message-ID: <729759941.2862556.1630676512695.JavaMail.zimbra@u-pem.fr> While i was re-reding the JEP, something struck me as odd, in the method typeTester, in case of the enum Color, the method values() is called on an instance of the enum but this method is static, the code should be case Color c -> System.out.println("Color with " + Color.values().length + " values"); From forax at univ-mlv.fr Fri Sep 3 14:00:20 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Fri, 3 Sep 2021 16:00:20 +0200 (CEST) Subject: Dominance in pattern matching for switch: Spec and Javac inconsistency In-Reply-To: <3A25E658-CA11-49B7-AD72-AA556098C2E5@selskabet.org> References: <1942616823.2516329.1630654194683.JavaMail.zimbra@u-pem.fr> <3A25E658-CA11-49B7-AD72-AA556098C2E5@selskabet.org> Message-ID: <1349000086.2878006.1630677620733.JavaMail.zimbra@u-pem.fr> > From: "Jesper Steen M?ller" > To: "Remi Forax" , "Ilyas Selimov" > > Cc: "compiler-dev" > Sent: Vendredi 3 Septembre 2021 10:30:58 > Subject: Re: Dominance in pattern matching for switch: Spec and Javac > inconsistency > Hi Hi, moved to spec-expert because there is an open question at the end, > If I understand correctly, a guarded switch label effectively can't dominate any > other labels, since the spec doesn't go into trying to statically analyze > whether or not the guard could ever be false. > I guess the spec could mention that (but it would be a comment, since it follows > from the other rules). A guarded switch pattern can not dominate any other guarded switch pattern that have the same type pattern prefix, e.g. case Integer i && foo(i) vs case Integer i && bar(i) but a guarded switch dominates it's prefix, e.eg case Integer i && foo(i) dominates case Integer. But there is no rule between a guarded pattern like case Integer i && foo(i) and case 1, so they can appear in any order. We may want to revisit that because from my own experience, it's not rare to add/remove guards to a pattern during the development, so forcing case 1 to dominate case Integer i && foo(i) means that you do not have to re-organize the case when you remove the guard. But I don't know if it's a good idea or not ? > -Jesper R?mi >> On 3 Sep 2021, at 09.29, Remi Forax < [ mailto:forax at univ-mlv.fr | >> forax at univ-mlv.fr ] > wrote: >>> From: "Ilyas Selimov" < [ mailto:ilyas.selimov at jetbrains.com | >>> ilyas.selimov at jetbrains.com ] > >>> To: "compiler-dev" < [ mailto:compiler-dev at openjdk.java.net | >>> compiler-dev at openjdk.java.net ] > >>> Sent: Vendredi 3 Septembre 2021 07:27:42 >>> Subject: Dominance in pattern matching for switch: Spec and Javac inconsistency >>> Hello! >>> The next code compiles correctly, but it seems to contradict the dominance >>> rules: >>> void test(Integer i) { >>> switch (i) { >>> case Integer in && in != null: >>> break; >>> case 1: >>> break; >>> case default: >>> break; >>> } >>> } >>>> A switch label that has a pattern case label element p dominates another switch >>>> label that has a constant case label element c if either of the following is >>> > true: >>>> - the type of c is a primitive type and its wrapper class (5.1.7) is a subtype >>> > of the erasure of the type of p. >>> Maybe the type of p should also be total for the type of selector expression >>> like in the rules for pattern-over-null dominance? >> For me, the problem comes from the guard, if you write the same code without the >> guard, the compiler correctly emits an error >> void test(Integer i) { >> switch (i) { >> case Integer in: >> break; >> case 1: >> break; >> case default: >> break; >> } >> } >> for me, this rule should apply even if p is a guard, the type of p when p is a >> guard is the type of the type part (the left part) of a guard. >> So it's more a bug in the implementation than a spec issue. >> But given that Eclipse has the same issue, the spec should be clarified. >>> Thanks, >>> Ilyas >> regards, >> R?mi From brian.goetz at oracle.com Fri Sep 3 20:03:42 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 3 Sep 2021 16:03:42 -0400 Subject: JEP 406 In-Reply-To: <729759941.2862556.1630676512695.JavaMail.zimbra@u-pem.fr> References: <729759941.2862556.1630676512695.JavaMail.zimbra@u-pem.fr> Message-ID: Fixed. On 9/3/2021 9:41 AM, Remi Forax wrote: > While i was re-reding the JEP, something struck me as odd, > in the method typeTester, in case of the enum Color, the method values() is called on an instance of the enum but this method is static, > the code should be > case Color c -> System.out.println("Color with " + Color.values().length + " values"); > From brian.goetz at oracle.com Fri Sep 3 20:07:57 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 3 Sep 2021 16:07:57 -0400 Subject: Dominance in pattern matching for switch: Spec and Javac inconsistency In-Reply-To: <1349000086.2878006.1630677620733.JavaMail.zimbra@u-pem.fr> References: <1942616823.2516329.1630654194683.JavaMail.zimbra@u-pem.fr> <3A25E658-CA11-49B7-AD72-AA556098C2E5@selskabet.org> <1349000086.2878006.1630677620733.JavaMail.zimbra@u-pem.fr> Message-ID: > > If I understand correctly, a guarded switch label effectively > can't dominate any other labels, since the spec doesn't go into > trying to statically analyze whether or not the guard could ever > be false. > I guess the spec could mention that (but it would be a comment, > since it follows from the other rules). > > > A guarded switch pattern can not dominate any other guarded switch > pattern that have the same type pattern prefix, > e.g. > ? case Integer i && foo(i) > vs > ? case Integer i && bar(i) > > but a guarded switch dominates it's prefix, > e.eg > ? case Integer i && foo(i) dominates case Integer. You've got that backwards; `Integer i` dominates `Integer i && g` for all g. > But there is no rule between a guarded pattern like case Integer i && > foo(i) and case 1, so they can appear in any order. Correct, a type pattern `T` should dominate any constant case labels of type T, but you are right, thsi does not seem to work properly. Also doesn't work with 17ea: jshell> int x = 3; switch (x) { case Integer i:} x ==> 3 |? Error: |? the switch statement does not cover all possible input values |?? switch (x) { case Integer i:} |?? ^---------------------------^ From forax at univ-mlv.fr Sat Sep 4 09:14:20 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sat, 4 Sep 2021 11:14:20 +0200 (CEST) Subject: JEP 406 In-Reply-To: References: <729759941.2862556.1630676512695.JavaMail.zimbra@u-pem.fr> Message-ID: <665269252.3059483.1630746860218.JavaMail.zimbra@u-pem.fr> Thanks, R?mi > From: "Brian Goetz" > To: "Remi Forax" , "amber-spec-experts" > > Sent: Vendredi 3 Septembre 2021 22:03:42 > Subject: Re: JEP 406 > Fixed. > On 9/3/2021 9:41 AM, Remi Forax wrote: >> While i was re-reding the JEP, something struck me as odd, >> in the method typeTester, in case of the enum Color, the method values() is >> called on an instance of the enum but this method is static, >> the code should be >> case Color c -> System.out.println("Color with " + Color.values().length + " >> values"); From forax at univ-mlv.fr Sat Sep 4 09:25:57 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sat, 4 Sep 2021 11:25:57 +0200 (CEST) Subject: Dominance in pattern matching for switch: Spec and Javac inconsistency In-Reply-To: References: <1942616823.2516329.1630654194683.JavaMail.zimbra@u-pem.fr> <3A25E658-CA11-49B7-AD72-AA556098C2E5@selskabet.org> <1349000086.2878006.1630677620733.JavaMail.zimbra@u-pem.fr> Message-ID: <1711061040.3061251.1630747557620.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "Remi Forax" , "Jesper Steen M?ller" > > Cc: "Ilyas Selimov" , "amber-spec-experts" > > Sent: Vendredi 3 Septembre 2021 22:07:57 > Subject: Re: Dominance in pattern matching for switch: Spec and Javac > inconsistency >>> If I understand correctly, a guarded switch label effectively can't dominate any >>> other labels, since the spec doesn't go into trying to statically analyze >>> whether or not the guard could ever be false. >>> I guess the spec could mention that (but it would be a comment, since it follows >>> from the other rules). >> A guarded switch pattern can not dominate any other guarded switch pattern that >> have the same type pattern prefix, >> e.g. >> case Integer i && foo(i) >> vs >> case Integer i && bar(i) >> but a guarded switch dominates it's prefix, >> e.eg >> case Integer i && foo(i) dominates case Integer. > You've got that backwards; `Integer i` dominates `Integer i && g` for all g. yes ! >> But there is no rule between a guarded pattern like case Integer i && foo(i) and >> case 1, so they can appear in any order. > Correct, a type pattern `T` should dominate any constant case labels of type T, > but you are right, thsi does not seem to work properly. > Also doesn't work with 17ea: > jshell> int x = 3; switch (x) { case Integer i:} > x ==> 3 > | Error: > | the switch statement does not cover all possible input values > | switch (x) { case Integer i:} > | ^---------------------------^ About the relation between int and Integer, the spec allows boxing but not unboxing, i wonder if we enhance the spec to support unboxing, unboxing being like a pattern query with two possible outcome: do not match if null and match and extract the value if non null. Integer anInteger = ... switch(anInteger) { case null -> // called if null case int i -> // called for non null Integer } and Integer anInteger = ... switch(anInteger) { // NPE if null case int i -> // called for non null Integer } with case Integer dominating case int. R?mi From john.r.rose at oracle.com Sat Sep 4 18:09:24 2021 From: john.r.rose at oracle.com (John Rose) Date: Sat, 4 Sep 2021 18:09:24 +0000 Subject: Dominance in pattern matching for switch: Spec and Javac inconsistency In-Reply-To: <1349000086.2878006.1630677620733.JavaMail.zimbra@u-pem.fr> References: <1942616823.2516329.1630654194683.JavaMail.zimbra@u-pem.fr> <3A25E658-CA11-49B7-AD72-AA556098C2E5@selskabet.org> <1349000086.2878006.1630677620733.JavaMail.zimbra@u-pem.fr> Message-ID: On Sep 3, 2021, at 7:00 AM, forax at univ-mlv.fr wrote: > > > but a guarded switch dominates it's prefix, > e.eg > case Integer i && foo(i) dominates case Integer. > > But there is no rule between a guarded pattern like case Integer i && foo(i) and case 1, so they can appear in any order. > Surely that?s a simple oversight. FWIW, it would be possible to convert reasoning about value-set inclusion to reasoning about type dominance, in this case, by simulating T x && g(x) as an existential type (? extends T). I think that simulation has the right properties for guards, which is that (a) no two guards denote the same static value set and (b) any guarded value set is a subset (possibly improper) of the corresponding unguarded value set. From forax at univ-mlv.fr Mon Sep 6 06:58:28 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Mon, 6 Sep 2021 08:58:28 +0200 (CEST) Subject: Dominance in pattern matching for switch: Spec and Javac inconsistency In-Reply-To: References: <1942616823.2516329.1630654194683.JavaMail.zimbra@u-pem.fr> <3A25E658-CA11-49B7-AD72-AA556098C2E5@selskabet.org> <1349000086.2878006.1630677620733.JavaMail.zimbra@u-pem.fr> Message-ID: <253543138.42386.1630911508336.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "John Rose" > To: "Remi Forax" > Cc: "Jesper Steen M?ller" , "Ilyas Selimov" , "amber-spec-experts" > > Sent: Samedi 4 Septembre 2021 20:09:24 > Subject: Re: Dominance in pattern matching for switch: Spec and Javac inconsistency > On Sep 3, 2021, at 7:00 AM, forax at univ-mlv.fr wrote: >> >> >> but a guarded switch dominates it's prefix, >> e.eg >> case Integer i && foo(i) dominates case Integer. >> >> But there is no rule between a guarded pattern like case Integer i && foo(i) and >> case 1, so they can appear in any order. >> > > Surely that?s a simple oversight. > > FWIW, it would be possible to convert reasoning about value-set > inclusion to reasoning about type dominance, in this case, by > simulating T x && g(x) as an existential type (? extends T). > > I think that simulation has the right properties for guards, > which is that (a) no two guards denote the same static value > set and (b) any guarded value set is a subset (possibly improper) > of the corresponding unguarded value set. yes, apart that we can detect the case where both guards are identical. something we currently do not do BTW. R?mi From amaembo at gmail.com Mon Sep 6 09:12:50 2021 From: amaembo at gmail.com (Tagir Valeev) Date: Mon, 6 Sep 2021 16:12:50 +0700 Subject: Reiterate total pattern accepting null in switch Message-ID: Hello! Now, as we develop support in IntelliJ, we have a little bit of experience with patterns in switches. So far, the thing I dislike the most is that the total pattern matches null in the switch. I shared my concerns before and now they are basically the same, probably even stronger. Note that I'm not against total pattern matching null in general (e.g., as a nested pattern in deconstruction). But I still think that matching it at the top level of the switch is a mistake. Mentally, the total pattern is close to the default case. It's just more explicit and allows introducing a variable which could be convenient if the selector expression is something complex, like a method call. But the asymmetry in null handling adds confusion. Also, adding a guard means that we do not receive null at this branch anymore which is also confusing. E.g., `case Object obj` accepts null but `case Object obj && obj != null` is meaningless as `obj != null` is always true. Well, making the pattern non-total immediately requires adding a default case, so you cannot just add a guard and do nothing else. Still, it's mentally confusing: switch(x) { ... other cases case Object obj -> ... // null goes here } switch(x) { ... other cases case Object obj && obj != null -> ... // exclude null default -> // add default, as compiler requests. Now only 'null' is the remainder. But why it doesn't go here? } Finally, it complicates the automatic code analysis: to understand whether a given switch throws NPE unconditionally, we need to resolve all the pattern types and evaluate the type of selector expression. This limits our ability for interprocedural analysis, as for performance reasons, we can resolve references in the currently edited file only. I still think that only an explicit 'null' label should turn the switch into null-friendly mode. What do other experts think about this? Am I the only one who doesn't like this? In general, I'm very positive about pattern matching in switches. Aside from this nullability problem, everything else looks fine to me. With best regards, Tagir Valeev. From brian.goetz at oracle.com Mon Sep 6 13:23:41 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 6 Sep 2021 09:23:41 -0400 Subject: Dominance in pattern matching for switch: Spec and Javac inconsistency In-Reply-To: <253543138.42386.1630911508336.JavaMail.zimbra@u-pem.fr> References: <1942616823.2516329.1630654194683.JavaMail.zimbra@u-pem.fr> <3A25E658-CA11-49B7-AD72-AA556098C2E5@selskabet.org> <1349000086.2878006.1630677620733.JavaMail.zimbra@u-pem.fr> <253543138.42386.1630911508336.JavaMail.zimbra@u-pem.fr> Message-ID: > yes, > apart that we can detect the case where both guards are identical. > > something we currently do not do BTW. > That we don't do that now is a deliberate choice.? Even this is stepping onto the slippery slope; the ability to reason about guards falls off a cliff pretty quickly, and I'm not sure that handling the "easy cases" is doing the user a favor, when it risks lulling them into a false sense of confidence.? (We see this all the time with type inference; the better type inference is, the more upset people get when it doesn't read their mind.) We could "easily" handle this: ??? case int i && i > 0: ??? case int i && i <= 0; (and some languages do.)? But even the short hop to ??? case int i && (i %2) == 0: ??? case int i && (i %2) == 1; is harder.? IntelliJ does heroics with abstract interpretation to find these things, which is great for an IDE, but less appropriate for a language spec.? So the question is, if we're going to venture onto that field, where do we draw the line?? Even "you specified exactly the same guard", once you get beyond a purely lexical definition of "same", is going to have holes. The danger of attempting something so inherently incomplete is that we will invariably get drawn into a stream of "you do it here, but not there, that's a bug", which leads either to resources invested in low-value extensions, or low-value arguments about why we're not going to take then 342nd step (because, you already took the previous 341!) So for now, we've drawn the line at "we're not gonna try"; we can always try harder later, since all that will do is make some switches that were already total but we didn't know it, into "total and we know it." From brian.goetz at oracle.com Mon Sep 6 13:46:19 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 6 Sep 2021 09:46:19 -0400 Subject: Reiterate total pattern accepting null in switch In-Reply-To: References: Message-ID: <425fec2f-098e-4b05-c814-acf74aa7bb44@oracle.com> On 9/6/2021 5:12 AM, Tagir Valeev wrote: > Hello! > > Now, as we develop support in IntelliJ, we have a little bit of > experience with patterns in switches. So far, the thing I dislike the > most is that the total pattern matches null in the switch. I shared my > concerns before and now they are basically the same, probably even > stronger. Note that I'm not against total pattern matching null in > general (e.g., as a nested pattern in deconstruction). But I still > think that matching it at the top level of the switch is a mistake. > Mentally, the total pattern is close to the default case. I think there are several issues here; let's try to tease them apart. The main problem, as I see it, is not one of whether we picked the right default, or whether that default is unfamiliar (though these are both valid things to discuss), but that we are putting the user to a non-orthogonal choice.? They can say: ??? case Object o: and get binding and null-matching, or ??? default: and get neither binding nor null-matching. In some way, you are saying that there is a significant contingency where users want binding but not null-matching, and we're forcing users to take a package deal.? As a thought experiment, how differently would you feel if we had both nullable and non-nullable type patterns: ??? case String! s: ??? case String? s: If we had the ability to refine the match in this way, then the choice of binding and null handling would be orthogonal: ??? case Object! o:?? // binding, no null ??? case Object? o:?? // binding, null ??? default:????????? // no binding, no null ??? null, default:??? // no binding, null So the first thought experiment I am asking you to do is whether, in this world, you would feel significantly differently. > Also, > adding a guard means that we do not receive null at this branch > anymore which is also confusing. Good point, I'll think on this a bit. To reiterate the motivation, the thing we're going for here is the consistency that a switch of nested patterns and a nested switch are the same: ??? case Box(Foo f): ??? case Box(Object o): is the same as ??? case Box b: ??????? switch (b.get()) { ??????????? case Foo f: ??????????? case Object o: ??????? } ??? } If we treat the null at the top level, we get a different kind of asymmetry.? What we're banking on (which could be wrong) is that its better to rip off the band-aid rather than cater to legacy assumptions about switch. I think what you are saying here is that switch is so weird that it is just a matter of pick your asymmetry, and the argument for moving it to the top level is that this is weirdness people are already used to.? This may be true, though I worry that it is just that people are not *yet* used to nested switches, but they'll be annoyed when they get bit by refactoring issues. > E.g., `case Object obj` accepts null > but `case Object obj && obj != null` is meaningless as `obj != null` > is always true. Well, making the pattern non-total immediately > requires adding a default case, so you cannot just add a guard and do > nothing else. Still, it's mentally confusing: > > switch(x) { > ... other cases > case Object obj -> ... // null goes here > } > > switch(x) { > ... other cases > case Object obj && obj != null -> ... // exclude null > default -> // add default, as compiler requests. Now only 'null' is > the remainder. But why it doesn't go here? > } OK, but write those cases out nested one level in Box(); are you not equally unhappy there? From forax at univ-mlv.fr Mon Sep 6 19:51:17 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 6 Sep 2021 21:51:17 +0200 (CEST) Subject: Reiterate total pattern accepting null in switch In-Reply-To: <425fec2f-098e-4b05-c814-acf74aa7bb44@oracle.com> References: <425fec2f-098e-4b05-c814-acf74aa7bb44@oracle.com> Message-ID: <1469563707.541865.1630957877558.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Brian Goetz" > To: "Tagir Valeev" , "amber-spec-experts" > Sent: Lundi 6 Septembre 2021 15:46:19 > Subject: Re: Reiterate total pattern accepting null in switch > On 9/6/2021 5:12 AM, Tagir Valeev wrote: >> Hello! >> >> Now, as we develop support in IntelliJ, we have a little bit of >> experience with patterns in switches. So far, the thing I dislike the >> most is that the total pattern matches null in the switch. I shared my >> concerns before and now they are basically the same, probably even >> stronger. Note that I'm not against total pattern matching null in >> general (e.g., as a nested pattern in deconstruction). But I still >> think that matching it at the top level of the switch is a mistake. >> Mentally, the total pattern is close to the default case. > > I think there are several issues here; let's try to tease them apart. > > The main problem, as I see it, is not one of whether we picked the right > default, or whether that default is unfamiliar (though these are both > valid things to discuss), but that we are putting the user to a > non-orthogonal choice.? They can say: > > ??? case Object o: > > and get binding and null-matching, or > > ??? default: > > and get neither binding nor null-matching. > > In some way, you are saying that there is a significant contingency > where users want binding but not null-matching, and we're forcing users > to take a package deal.? As a thought experiment, how differently would > you feel if we had both nullable and non-nullable type patterns: > > ??? case String! s: > ??? case String? s: > > If we had the ability to refine the match in this way, then the choice > of binding and null handling would be orthogonal: > > ??? case Object! o:?? // binding, no null > ??? case Object? o:?? // binding, null > ??? default:????????? // no binding, no null > ??? null, default:??? // no binding, null > > So the first thought experiment I am asking you to do is whether, in > this world, you would feel significantly differently. There is no binding available for "default" but the selector value of the switch is available. switch(anObject) { default: // no binding but one can use 'anObject' directly } so i'm not sure to fully understand the point your are trying to make. > >> Also, >> adding a guard means that we do not receive null at this branch >> anymore which is also confusing. > > Good point, I'll think on this a bit. yes, if there is no default, the type pattern for a case which is not the last and the type pattern for the last case has a different meaning (for the record i'm fine with that), but combined with the current rules for the guarded patterns, this has some puzzling side effects. By example, using a guard that is simple enough for a human to see it is always true but not simple enough for the compiler so it is not constant folded, switch(x) { ... other cases case Object obj && x == x -> ... // exclude null case Object obj -> // null goes here } > > To reiterate the motivation, the thing we're going for here is the > consistency that a switch of nested patterns and a nested switch are the > same: > > ??? case Box(Foo f): > ??? case Box(Object o): > > is the same as > > ??? case Box b: > ??????? switch (b.get()) { > ??????????? case Foo f: > ??????????? case Object o: > ??????? } > ??? } > > If we treat the null at the top level, we get a different kind of > asymmetry.? What we're banking on (which could be wrong) is that its > better to rip off the band-aid rather than cater to legacy assumptions > about switch. And also we want to keep the legacy semantics of "default", we do not want the existing "default" inside the switch on String/Enum to have a different behavior from the existing ones. > > I think what you are saying here is that switch is so weird that it is > just a matter of pick your asymmetry, and the argument for moving it to > the top level is that this is weirdness people are already used to. > This may be true, though I worry that it is just that people are not > *yet* used to nested switches, but they'll be annoyed when they get bit > by refactoring issues. And there is also actually no way to write "case var o" which is IMHO the explicit way to convey the fact that the last case is total. Using "case var o" in all the examples above make the codes readable. I think the real issue here is that we are in the middle of introducing the different patterns, so the big picture is not in front of us yet. [...] R?mi From john.r.rose at oracle.com Mon Sep 6 21:09:09 2021 From: john.r.rose at oracle.com (John Rose) Date: Mon, 6 Sep 2021 21:09:09 +0000 Subject: Dominance in pattern matching for switch: Spec and Javac inconsistency In-Reply-To: References: <1942616823.2516329.1630654194683.JavaMail.zimbra@u-pem.fr> <3A25E658-CA11-49B7-AD72-AA556098C2E5@selskabet.org> <1349000086.2878006.1630677620733.JavaMail.zimbra@u-pem.fr> <253543138.42386.1630911508336.JavaMail.zimbra@u-pem.fr> Message-ID: FTR, I approve of not even stepping onto this slippery slope. Doing so would seem to promise something of value to users, a promise they?d probably feel we would break by the draconian restrictions (with or without complex case analysis on statically kennable expressions). Remi, this relates to your offhand comment about guards which are always true, as in case T x && alwaysTrue(x) vs. case T (no guard). Detecting always-true guards is about the same problem (with no solution) as detecting equivalent guards. In particular if G1 and G2 are somehow provably equivalent then G1==G2 is provably true and G1!=G2 is provably false. If G0 is provably true, then G1 and G1&G0 are provably equivalent, and so on. Statically, a guard should be treated as a coin flip independent of every other expression. (See also my comment about guards working, in static analysis, like existential subtypes of the guarded type.) ? John > On Sep 6, 2021, at 6:23 AM, Brian Goetz wrote: > > >> yes, >> apart that we can detect the case where both guards are identical. >> >> something we currently do not do BTW. >> > > That we don't do that now is a deliberate choice. Even this is stepping onto the slippery slope; the ability to reason about guards falls off a cliff pretty quickly, and I'm not sure that handling the "easy cases" is doing the user a favor, when it risks lulling them into a false sense of confidence. (We see this all the time with type inference; the better type inference is, the more upset people get when it doesn't read their mind.) > > We could "easily" handle this: > > case int i && i > 0: > case int i && i <= 0; > > (and some languages do.) But even the short hop to > > case int i && (i %2) == 0: > case int i && (i %2) == 1; > > is harder. IntelliJ does heroics with abstract interpretation to find these things, which is great for an IDE, but less appropriate for a language spec. So the question is, if we're going to venture onto that field, where do we draw the line? Even "you specified exactly the same guard", once you get beyond a purely lexical definition of "same", is going to have holes. > > The danger of attempting something so inherently incomplete is that we will invariably get drawn into a stream of "you do it here, but not there, that's a bug", which leads either to resources invested in low-value extensions, or low-value arguments about why we're not going to take then 342nd step (because, you already took the previous 341!) > > So for now, we've drawn the line at "we're not gonna try"; we can always try harder later, since all that will do is make some switches that were already total but we didn't know it, into "total and we know it." > > From john.r.rose at oracle.com Mon Sep 6 21:14:40 2021 From: john.r.rose at oracle.com (John Rose) Date: Mon, 6 Sep 2021 21:14:40 +0000 Subject: Dominance in pattern matching for switch: Spec and Javac inconsistency In-Reply-To: References: <1942616823.2516329.1630654194683.JavaMail.zimbra@u-pem.fr> <3A25E658-CA11-49B7-AD72-AA556098C2E5@selskabet.org> <1349000086.2878006.1630677620733.JavaMail.zimbra@u-pem.fr> <253543138.42386.1630911508336.JavaMail.zimbra@u-pem.fr> Message-ID: <908BD727-1128-473C-9E8D-250EE8D73A2D@oracle.com> P.S. If a Java expression X is (for whatever reason) compiled to an indy instruction, and that instruction has a BSM that flips coins or bumps counters or uses resources, then *two identical copies* of X will not be equivalent. (This relates to some conversation Brian and I had about ?constant expressions?, along the lines of ?how constant does a constant expression need to be?? One aspect of that set of problems is that the user often expects to be able to use the value of a constant expression in multiple places, at least in some desugaring schemes, so it should be clone-able without loss of information; that lets us write the desugaring without generating static temp variables. But an indy-generating expression is not clone-able, unless its BSM is constrained somehow.) On Sep 6, 2021, at 2:09 PM, John Rose > wrote: FTR, I approve of not even stepping onto this slippery slope. Doing so would seem to promise something of value to users, a promise they?d probably feel we would break by the draconian restrictions (with or without complex case analysis on statically kennable expressions). Remi, this relates to your offhand comment about guards which are always true, as in case T x && alwaysTrue(x) vs. case T (no guard). Detecting always-true guards is about the same problem (with no solution) as detecting equivalent guards. In particular if G1 and G2 are somehow provably equivalent then G1==G2 is provably true and G1!=G2 is provably false. If G0 is provably true, then G1 and G1&G0 are provably equivalent, and so on. Statically, a guard should be treated as a coin flip independent of every other expression. (See also my comment about guards working, in static analysis, like existential subtypes of the guarded type.) ? John From forax at univ-mlv.fr Tue Sep 7 13:41:51 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 7 Sep 2021 15:41:51 +0200 (CEST) Subject: Dominance in pattern matching for switch: Spec and Javac inconsistency In-Reply-To: <908BD727-1128-473C-9E8D-250EE8D73A2D@oracle.com> References: <3A25E658-CA11-49B7-AD72-AA556098C2E5@selskabet.org> <1349000086.2878006.1630677620733.JavaMail.zimbra@u-pem.fr> <253543138.42386.1630911508336.JavaMail.zimbra@u-pem.fr> <908BD727-1128-473C-9E8D-250EE8D73A2D@oracle.com> Message-ID: <1315587075.910274.1631022111924.JavaMail.zimbra@u-pem.fr> > From: "John Rose" > To: "Brian Goetz" > Cc: "Remi Forax" , "Jesper Steen M?ller" > , "Ilyas Selimov" , > "amber-spec-experts" > Sent: Lundi 6 Septembre 2021 23:14:40 > Subject: Re: Dominance in pattern matching for switch: Spec and Javac > inconsistency > P.S. If a Java expression X is (for whatever reason) > compiled to an indy instruction, and that instruction > has a BSM that flips coins or bumps counters or > uses resources, then *two identical copies* of X > will not be equivalent. > (This relates to some conversation Brian and I > had about ?constant expressions?, along the lines > of ?how constant does a constant expression need > to be?? One aspect of that set of problems is that > the user often expects to be able to use the value > of a constant expression in multiple places, at > least in some desugaring schemes, so it should > be clone-able without loss of information; that > lets us write the desugaring without generating > static temp variables. But an indy-generating > expression is not clone-able, unless its BSM is > constrained somehow.) BSM used by Java (the language) has to be constrained anyway, otherwise you loose the part of the ecosystem that relies on static analysis to work, Graal native image or Android, come to my mind. I don't think any future Java translation strategies can use a BSM which is not refentially transparent. BTW, it's why i want the implementation of the translation strategy for the switch to share prefix patterns, at runtime, you execute only one expression for one pattern even if the same pattern is present in different cases, so you do not have to clone things. The drawback is that you have to transform every patterns (the thingy defined between "case" and ":"/"->") to a method handle at runtime, so peak performance is not an issue but startup is slow. R?mi >> On Sep 6, 2021, at 2:09 PM, John Rose < [ mailto:john.r.rose at oracle.com | >> john.r.rose at oracle.com ] > wrote: >> FTR, I approve of not even stepping onto this slippery >> slope. Doing so would seem to promise something of >> value to users, a promise they?d probably feel we would >> break by the draconian restrictions (with or without >> complex case analysis on statically kennable expressions). >> Remi, this relates to your offhand comment about guards >> which are always true, as in case T x && alwaysTrue(x) >> vs. case T (no guard). Detecting always-true guards is >> about the same problem (with no solution) as detecting >> equivalent guards. In particular if G1 and G2 are somehow >> provably equivalent then G1==G2 is provably true and >> G1!=G2 is provably false. If G0 is provably true, then >> G1 and G1&G0 are provably equivalent, and so on. >> Statically, a guard should be treated as a coin flip >> independent of every other expression. (See also >> my comment about guards working, in static >> analysis, like existential subtypes of the guarded >> type.) >> ? John From amaembo at gmail.com Wed Sep 8 05:15:19 2021 From: amaembo at gmail.com (Tagir Valeev) Date: Wed, 8 Sep 2021 12:15:19 +0700 Subject: Reiterate total pattern accepting null in switch In-Reply-To: <425fec2f-098e-4b05-c814-acf74aa7bb44@oracle.com> References: <425fec2f-098e-4b05-c814-acf74aa7bb44@oracle.com> Message-ID: Hello! > case Object! o: // binding, no null > case Object? o: // binding, null > default: // no binding, no null > null, default: // no binding, null > > So the first thought experiment I am asking you to do is whether, in > this world, you would feel significantly differently. This is definitely more clear, at least you have a visual sign that null is allowed there, and don't need any bigger context. Also, Kotlin programming experience helps here. However, this will allow introducing several null-friendly branches: case String? s -> ... case Number? n -> ... Now, we can do this with case null, String s -> ... case null, Number n -> ... And it's much more clear that something is wrong (and compiler error will be perfectly clear). When using the question mark, it's not clear whether we should prohibit this (as both cases are actually populated). In other words, this syntax would introduce its own problems. Though probably, it's justified to explore this syntax again, as we are approaching nested patterns. I'm not sure whether we need `Object!` though, just `Object` (non-null by default) would be enough. Also, having "String? s" pattern would allow enhancing instanceof to match null. Sometimes, it's really useful. In IntelliJ sources, we have 388 conditions like `$x$ == null || $x$ instanceof $t$`. Well, in most of the cases, the binding variable is unnecessary (it's usually like if(x == null || x instanceof SomeWrongType) return false) > To reiterate the motivation, the thing we're going for here is the > consistency that a switch of nested patterns and a nested switch are the > same: > > case Box(Foo f): > case Box(Object o): > > is the same as > > case Box b: > switch (b.get()) { > case Foo f: > case Object o: > } > } Yes, I remember that the motivation was like this. > If we treat the null at the top level, we get a different kind of > asymmetry. What we're banking on (which could be wrong) is that its > better to rip off the band-aid rather than cater to legacy assumptions > about switch. > > I think what you are saying here is that switch is so weird that it is > just a matter of pick your asymmetry, and the argument for moving it to > the top level is that this is weirdness people are already used to. Yes, exactly! > This may be true, though I worry that it is just that people are not > *yet* used to nested switches, but they'll be annoyed when they get bit > by refactoring issues. Probably you're right. > OK, but write those cases out nested one level in Box(); are you not > equally unhappy there? It's interesting. Let's assume we have Box, so String s is total on the box component. Am I correct in the interpretation of the following patterns? case Box(String s && s.isEmpty()) // String s && s.isEmpty() is not total on the box component, so Box(null) is not matched here case Box(String s) && s.isEmpty() // String s is total on the box component, so Box(null) is matched in nested pattern, and s.isEmpty() will produce NPE If yes, then yes, it's confusing. Here, having String? (to allow nulls) and String (to disallow nulls regardless of totality) would definitely make things more clear. With best regards, Tagir Valeev From forax at univ-mlv.fr Wed Sep 8 06:42:29 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 8 Sep 2021 08:42:29 +0200 (CEST) Subject: merging cases in between values and types Message-ID: <2062013998.1180753.1631083349599.JavaMail.zimbra@u-pem.fr> Hi all, the current spec support merging/fallthrough cases only when there is a case null, like case null, case Integer i -> // i can use 'i' here First, i'm not sure it's clearly written in the spec, i was not able to pinpoint the rule allowing it. Then i wonder if this special behavior is special because null is special or if it should work with any values of the type of the type pattern, i.e. it works because null is a possible value of Integer, in that case, it should also work with any other values like 42 case 42, case Integer i -> // i can use 'i' here So is null a special value or does this rule also work with any values. regards, R?mi From brian.goetz at oracle.com Wed Sep 8 15:08:59 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 8 Sep 2021 11:08:59 -0400 Subject: merging cases in between values and types In-Reply-To: <2062013998.1180753.1631083349599.JavaMail.zimbra@u-pem.fr> References: <2062013998.1180753.1631083349599.JavaMail.zimbra@u-pem.fr> Message-ID: It doesn't really matter, since 42 is subsumed into Integer anyway.? Null is the weirdo (as always.) On 9/8/2021 2:42 AM, Remi Forax wrote: > Hi all, > the current spec support merging/fallthrough cases only when there is a case null, > like > case null, case Integer i -> // i can use 'i' here > > First, i'm not sure it's clearly written in the spec, i was not able to pinpoint the rule allowing it. > Then i wonder if this special behavior is special because null is special or if it should work with any values of the type of the type pattern, > i.e. it works because null is a possible value of Integer, in that case, it should also work with any other values like 42 > > case 42, case Integer i -> // i can use 'i' here > > So is null a special value or does this rule also work with any values. > > regards, > R?mi > > From forax at univ-mlv.fr Wed Sep 8 16:15:18 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 8 Sep 2021 18:15:18 +0200 (CEST) Subject: merging cases in between values and types In-Reply-To: References: <2062013998.1180753.1631083349599.JavaMail.zimbra@u-pem.fr> Message-ID: <1092846518.1624345.1631117718550.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "Remi Forax" , "amber-spec-experts" > > Sent: Mercredi 8 Septembre 2021 17:08:59 > Subject: Re: merging cases in between values and types > It doesn't really matter, since 42 is subsumed into Integer anyway. Null is the > weirdo (as always.) ??, currently we allow null but not 42. R?mi > On 9/8/2021 2:42 AM, Remi Forax wrote: >> Hi all, >> the current spec support merging/fallthrough cases only when there is a case >> null, >> like >> case null, case Integer i -> // i can use 'i' here >> First, i'm not sure it's clearly written in the spec, i was not able to pinpoint >> the rule allowing it. >> Then i wonder if this special behavior is special because null is special or if >> it should work with any values of the type of the type pattern, >> i.e. it works because null is a possible value of Integer, in that case, it >> should also work with any other values like 42 >> case 42, case Integer i -> // i can use 'i' here >> So is null a special value or does this rule also work with any values. >> regards, >> R?mi From amaembo at gmail.com Sat Sep 11 06:28:07 2021 From: amaembo at gmail.com (Tagir Valeev) Date: Sat, 11 Sep 2021 13:28:07 +0700 Subject: Using switch patterns to rewrite Arrays.deepHashCode Message-ID: Hello! I just was thinking about good samples where switch patterns could be useful. One idea I have is Arrays.deepHashCode. The current implementation uses a helper method and queries getClass(): public static int deepHashCode(Object a[]) { if (a == null) return 0; int result = 1; for (Object element : a) { final int elementHash; final Class cl; if (element == null) elementHash = 0; else if ((cl = element.getClass().getComponentType()) == null) elementHash = element.hashCode(); else if (element instanceof Object[]) elementHash = deepHashCode((Object[]) element); else elementHash = primitiveArrayHashCode(element, cl); result = 31 * result + elementHash; } return result; } private static int primitiveArrayHashCode(Object a, Class cl) { return (cl == byte.class) ? hashCode((byte[]) a) : (cl == int.class) ? hashCode((int[]) a) : (cl == long.class) ? hashCode((long[]) a) : (cl == char.class) ? hashCode((char[]) a) : (cl == short.class) ? hashCode((short[]) a) : (cl == boolean.class) ? hashCode((boolean[]) a) : (cl == double.class) ? hashCode((double[]) a) : // If new primitive types are ever added, this method must be // expanded or we will fail here with ClassCastException. hashCode((float[]) a); } It can be simplified with patterns: public static int deepHashCode(Object[] a) { if (a == null) return 0; int result = 1; for (Object element : a) { final int elementHash = switch(element) { case null -> 0; case Object[] arr -> deepHashCode(arr); case byte[] arr -> hashCode(arr); case short[] arr -> hashCode(arr); case char[] arr -> hashCode(arr); case int[] arr -> hashCode(arr); case long[] arr -> hashCode(arr); case float[] arr -> hashCode(arr); case double[] arr -> hashCode(arr); default -> element.hashCode(); }; result = 31 * result + elementHash; } return result; } Here we see good use of case null. One problem is the possible (though unlikely) appearance of new primitive types. In this case, the default branch will be silently taken. Well, we can add an assert: default -> { assert !element.getClass().isArray(); yield element.hashCode(); } Probably we need a sealed supertype for all arrays that permits all final primitive subtypes and non-sealed Object[] subtype? In this case, we could make a sub-switch and rely on the compiler: final int elementHash = switch(element) { case null -> 0; case AnyArray anyArray -> switch(anyArray) { case Object[] arr -> deepHashCode(arr); case byte[] arr -> hashCode(arr); case short[] arr -> hashCode(arr); case char[] arr -> hashCode(arr); case int[] arr -> hashCode(arr); case long[] arr -> hashCode(arr); case float[] arr -> hashCode(arr); case double[] arr -> hashCode(arr); // no default case! }; default -> element.hashCode(); }; Well, this is probably a huge amount of work with little benefit, though probably something similar is being baked in Valhalla? In any case, I like the new version more than the original one. With best regards, Tagir Valeev. From brian.goetz at oracle.com Sat Sep 11 13:48:05 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 11 Sep 2021 09:48:05 -0400 Subject: Using switch patterns to rewrite Arrays.deepHashCode In-Reply-To: References: Message-ID: <9591aa13-766d-523a-4c55-fdb6699e9ab8@oracle.com> Thanks Tagir, this is a helpful exploration.? There are lots of places in the JDK (and the world beyond) that assume "eight primitive types, no more"; finding them is a game of whack-a-mole.? The real question is how many of them would fall back to something reasonable when the ninth primitive shows up, vs how many would blow up. THe good news is that, because primitives extend Object, code that says "do X for primitives, otherwise fall back to Object::hashCode (or ::toString or whatever) is likely to usually work. A synthetic "array" supertype is something we've discussed a few times, but always got distracted before coming to any conclusions. > final int elementHash = switch(element) { > case null -> 0; > case AnyArray anyArray -> switch(anyArray) { > case Object[] arr -> deepHashCode(arr); > case byte[] arr -> hashCode(arr); > case short[] arr -> hashCode(arr); > case char[] arr -> hashCode(arr); > case int[] arr -> hashCode(arr); > case long[] arr -> hashCode(arr); > case float[] arr -> hashCode(arr); > case double[] arr -> hashCode(arr); > // no default case! > }; > default -> element.hashCode(); > }; You would need to reorder this switch!? Because byte[] will be a subtype of Object[]. From john.r.rose at oracle.com Sat Sep 11 18:42:02 2021 From: john.r.rose at oracle.com (John Rose) Date: Sat, 11 Sep 2021 18:42:02 +0000 Subject: Using switch patterns to rewrite Arrays.deepHashCode In-Reply-To: <9591aa13-766d-523a-4c55-fdb6699e9ab8@oracle.com> References: <9591aa13-766d-523a-4c55-fdb6699e9ab8@oracle.com> Message-ID: <813AD5A4-7FDD-4779-99CA-10FC688CBDA6@oracle.com> On Sep 11, 2021, at 6:48 AM, Brian Goetz > wrote: You would need to reorder this switch! Because byte[] will be a subtype of Object[]. In Valhalla, that is. Putting Object[] at the bottom will absorb any and all ?new primitives?. In fact, the ?old primitives? will also be absorbed by Object[], so in Valhalla the switch statement is not required, but may be simply a hand-optimization (to get hand-specialized code for some known types). That sort of hand-optimization wants to be replaced by JVM and language support for template-like specialization. Putting Object[] at the top forces a re-evaluation when Valhalla comes, since the code will break. From john.r.rose at oracle.com Sat Sep 11 18:48:15 2021 From: john.r.rose at oracle.com (John Rose) Date: Sat, 11 Sep 2021 18:48:15 +0000 Subject: Using switch patterns to rewrite Arrays.deepHashCode In-Reply-To: <813AD5A4-7FDD-4779-99CA-10FC688CBDA6@oracle.com> References: <9591aa13-766d-523a-4c55-fdb6699e9ab8@oracle.com> <813AD5A4-7FDD-4779-99CA-10FC688CBDA6@oracle.com> Message-ID: On Sep 11, 2021, at 11:42 AM, John Rose > wrote: Putting Object[] at the top forces a re-evaluation when Valhalla comes, since the code will break. P.S. I wonder if there is a compatibility move where errors which arise from changed type relations (int <: Object) are downgraded to warnings, at least for a while. That would cause a dominating `case Object[] x:` to warn not error. I think I can hear Dan saying, ?ehh, not worth the trouble.? From gavin.bierman at oracle.com Mon Sep 13 15:00:09 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Mon, 13 Sep 2021 15:00:09 +0000 Subject: merging cases in between values and types In-Reply-To: <2062013998.1180753.1631083349599.JavaMail.zimbra@u-pem.fr> References: <2062013998.1180753.1631083349599.JavaMail.zimbra@u-pem.fr> Message-ID: <63C9A991-8498-40E9-8985-A7A9ECAEC1A9@oracle.com> Hi Remi, Sorry for the slow reply: > On 8 Sep 2021, at 07:42, Remi Forax wrote: > > Hi all, > the current spec support merging/fallthrough cases only when there is a case null, > like > case null, case Integer i -> // i can use 'i' here > > First, i'm not sure it's clearly written in the spec, i was not able to pinpoint the rule allowing it. In 14.11.1 there is a bunch of well-formedness conditions listed for switch labels. The paragraph begins ?For every switch label in a switch block, all of the following must be true, otherwise a compile-time error occurs?? What follows is a bunch of rules that spell out which switch labels are correctly formed, and which are not. The two rules that cover your question here are: ? If a switch label has a constant case label element then if the switch label also has other case element labels they must be either a constant case label element, the default case label element, or the null case label element. and ? If a switch label has a null case label element then if the switch label also has any pattern case element labels, they must be type patterns (14.30.1). So the first rule forbids you from writing case 42, Integer i. The second rule allows you to say case null, Integer i (and case Integer i, null actually.) So, yes, the null is treated specially. Indeed it?s behaviour is NOT about fall through really. When you write case null, Foo f you are not asking for the type pattern test to be carried out if the value is null (it might fail!). Nor are you asking for it to be ignored (cos then the pattern variable will not be initialised). You?re actually saying ?if it?s null then it matches AND the variable i is initialised to null?. So it?s being treated like a special pattern label. If we were to give it an alternative syntax, we might write ?case Foo? f? instead of ?case null, Foo f?. Hope this helps, Gavin > Then i wonder if this special behavior is special because null is special or if it should work with any values of the type of the type pattern, > i.e. it works because null is a possible value of Integer, in that case, it should also work with any other values like 42 > > case 42, case Integer i -> // i can use 'i' here > > So is null a special value or does this rule also work with any values. > > regards, > R?mi > > From forax at univ-mlv.fr Tue Sep 14 08:38:31 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 14 Sep 2021 10:38:31 +0200 (CEST) Subject: Factory methods & the language model In-Reply-To: <617ab0ce-4a93-7705-77c1-039cbae21676@oracle.com> References: <83A0DA05-45F2-43FB-9E67-7DEEFD46BB34@oracle.com> <865F5DDA-9A4D-4144-9162-B3FC46533A64@oracle.com> <67D21363-2592-426D-AE58-ECCE2E072DE3@oracle.com> <617ab0ce-4a93-7705-77c1-039cbae21676@oracle.com> Message-ID: <173911978.797118.1631608711740.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Brian Goetz" > To: "daniel smith" , "Dan Heidinga" > Cc: "valhalla-spec-experts" > Sent: Vendredi 10 Septembre 2021 20:25:50 > Subject: Re: Factory methods & the language model >> I'm not particularly interested in settling on a bikeshed color, but am >> interested in the general mood for pursuing this direction at all. (And not >> necessarily right away, just?is this a direction we think we'll be going?) >> >> A few observations/questions: >> >> - 'new Foo()' traditionally guarantees fresh instance creation for identity >> classes. Primitive classes relax this, since of course there is no unique >> identity to talk about. Would we be happy with a language that relaxes this >> further, such that 'new Foo()' can return an arbitrary Foo instance (or maybe >> even null)? Or would we want to pursue a different, factory-specific invocation >> syntax? (And if so, should primitive classes use it too?) > > Let me offer some context from Amber that might be helpful, regarding > whether we might want "factory" to be a language feature. > [...] > > Example 2 -- "with" expressions / reconstructors.? A number of > interesting features come out of the pairing of constructors and > deconstruction patterns with the same (or compatible) argument lists, > such as `with` expressions (`point with { x = 3 }`). Handling this > involves doing multiple overload selection operations, first to find a > deconstructor that yields the right bindings, and then to find a > compatible constructor that accepts the resulting exploded state. > > Among the many problems of doing this (including the fact that parameter > names are not required to be stable or meaningful), we also have the > problem of "what if the class doesn't have a constructor, but a > factory."? This would require the language to have a notion of factory > method (e.g., `factory newPoint(int x, int y)`) so the compiler could > try to match up a compatible factory with the deconstructor. I think there is a way to not introduce a weird with expression syntax by piggybacking on the fact that a record is a weird tuple. A record in Java is not just a tuple, i.e. a vector of values, but because all components are named also a compact key/value set. The "with" expression is a case where we want to see a record as key/value set more than as a vector of values. If we have a syntax to construct a record as a key/value set, this syntax can be slightly extended to express a "with" expression. By example, if we have a syntax like Point p = Point { x: 3, y: 4 }; Then the syntax for the with expression will be to something like Point p2 = Point { x: 7, p }; I can hear you saying that i'm trying to trick you to add a new syntax for creating a record which is bigger that just the "with" expression. And that's partially true. I believe that the "key/value set" syntax for a record is something we should introduce in Amber anyway because it's a declarative syntax, the same way a Stream is, making the code easier to read. And people want a syntax like this so badly that they are up to writing a full builder pattern in their code just for being able to see the name of the parameters when creating complex objects. regards, R?mi From gavin.bierman at oracle.com Tue Sep 14 16:03:24 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Tue, 14 Sep 2021 16:03:24 +0000 Subject: Fwd: Dominance in pattern matching for switch: Spec and Javac inconsistency References: <04A1BD76-E178-4F6E-A0F0-C321A8C49C52@oracle.com> Message-ID: <07F85643-64E5-4685-AF0F-15429401FF7A@oracle.com> Begin forwarded message: From: Gavin Bierman > Subject: Re: Dominance in pattern matching for switch: Spec and Javac inconsistency Date: 14 September 2021 at 17:02:50 BST To: Ilyas Selimov > Cc: "compiler-dev at openjdk.java.net" > Thanks Ilyas, Yes, this is a small bug in the spec which is causing your confusion. When it was first drafted it there was only one pattern case label element - a type pattern. By the time we published, there were two more - parenthesised patterns and guarded patterns. The rule didn?t get updated :-( The rule you read only applies to *type* patterns. We need a couple of extra rules for parenthesised and guarded patterns. As it happens the compiler is correct here. We treat a guarded pattern as a subset pattern, unless the guard is a constant expression whose value is true (a rare case!). So, in your example, we see that the first pattern matches *some* integers. We don?t know which ones, so we can?t be sure that it dominates the integer value 1. So the code is correct. We could attempt to look at the guards to see what we can infer statically, but I think this unlikely to be a fruitful direction to take. Thanks, Gavin On 3 Sep 2021, at 06:27, Ilyas Selimov > wrote: Hello! The next code compiles correctly, but it seems to contradict the dominance rules: void test(Integer i) { switch (i) { case Integer in && in != null: break; case 1: break; case default: break; } } A switch label that has a pattern case label element p dominates another switch label that has a constant case label element c if either of the following is true: - the type of c is a primitive type and its wrapper class (5.1.7) is a subtype of the erasure of the type of p. Maybe the type of p should also be total for the type of selector expression like in the rules for pattern-over-null dominance? Thanks, Ilyas From forax at univ-mlv.fr Wed Sep 15 16:06:02 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 15 Sep 2021 18:06:02 +0200 (CEST) Subject: merging cases in between values and types In-Reply-To: <63C9A991-8498-40E9-8985-A7A9ECAEC1A9@oracle.com> References: <2062013998.1180753.1631083349599.JavaMail.zimbra@u-pem.fr> <63C9A991-8498-40E9-8985-A7A9ECAEC1A9@oracle.com> Message-ID: <1422085677.1699630.1631721962349.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Gavin Bierman" > To: "Remi Forax" > Cc: "amber-spec-experts" > Sent: Lundi 13 Septembre 2021 17:00:09 > Subject: Re: merging cases in between values and types > Hi Remi, Hi Gavin, > > Sorry for the slow reply: > >> On 8 Sep 2021, at 07:42, Remi Forax wrote: >> >> Hi all, >> the current spec support merging/fallthrough cases only when there is a case >> null, >> like >> case null, case Integer i -> // i can use 'i' here >> >> First, i'm not sure it's clearly written in the spec, i was not able to pinpoint >> the rule allowing it. > > In 14.11.1 there is a bunch of well-formedness conditions listed for switch > labels. The paragraph begins ?For every switch label in a switch block, all of > the following must be true, otherwise a compile-time error occurs?? What > follows is a bunch of rules that spell out which switch labels are correctly > formed, and which are not. The two rules that cover your question here are: > > > ? If a switch label has a constant case label element then if the switch label > also has other case element labels they must be either a constant case label > element, the default case label element, or the null case label element. > > and > > ? If a switch label has a null case label element then if the switch label also > has any pattern case element labels, they must be type patterns (14.30.1). Thanks, i've overlook the rules of 14.11.1. The last point treat null has a special value, and i'm proposing to extend that rule to any constant not just null, because i don't see why null is a special case (sorry) here. This rule works because the type of null is a subtype of all the types. But we know that while this is true now, it will not be true in the future when we will introduce the primitive class. So instead of adding a special rule for null now to specialize it again when Valhalla step 1 lands, i think it's better to stick with a more general rule that say that you can have a case with a constant with a case with a type pattern (or a guard that starts with that type pattern) if the type of the constant is a subtype of the type of the type pattern. This will work with case null, Integer i: case "foo", String s && ...: and will not allow case null, APrimitiveClass apc: in the future. > > > So the first rule forbids you from writing case 42, Integer i. The second rule > allows you to say case null, Integer i (and case Integer i, null actually.) > > So, yes, the null is treated specially. Indeed it?s behaviour is NOT about fall > through really. When you write case null, Foo f you are not asking for the type > pattern test to be carried out if the value is null (it might fail!). Nor are > you asking for it to be ignored (cos then the pattern variable will not be > initialised). You?re actually saying ?if it?s null then it matches AND the > variable i is initialised to null?. So it?s being treated like a special > pattern label. If we were to give it an alternative syntax, we might write > ?case Foo? f? instead of ?case null, Foo f?. > > Hope this helps, > > Gavin regards, R?mi > > >> Then i wonder if this special behavior is special because null is special or if >> it should work with any values of the type of the type pattern, >> i.e. it works because null is a possible value of Integer, in that case, it >> should also work with any other values like 42 >> >> case 42, case Integer i -> // i can use 'i' here >> >> So is null a special value or does this rule also work with any values. >> >> regards, >> R?mi >> From james.laskey at oracle.com Thu Sep 16 13:28:41 2021 From: james.laskey at oracle.com (Jim Laskey) Date: Thu, 16 Sep 2021 13:28:41 +0000 Subject: String Tapas Redux: Beyond mere string interpolation Message-ID: Amber experts, Now that JDK 17 has been plated and left the kitchen, we should have a look-see at one of the new menu items Brian and I have had on a slow boil for these last few months; Templated Strings. Before you start shouting out, "Templated Strings? This isn't what I ordered! The subject said string interpolation!!!", take the time to follow this link https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md. After reading, we hope you'll see that the offering is much better than interpolation meat and potatoes. Cheers, -- Jim From forax at univ-mlv.fr Fri Sep 17 00:10:16 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 17 Sep 2021 02:10:16 +0200 (CEST) Subject: String Tapas Redux: Beyond mere string interpolation In-Reply-To: References: Message-ID: <1691075592.2270344.1631837416140.JavaMail.zimbra@u-pem.fr> Brian explicitly ask me to not talk about invokedynamic so i will not say that there is already an existing protocol between invokedynamic and a user defined implementation, it's the bootstrap method, Let's talk about the elephant in the room: macro. Templated Strings as currently defined is indiscernible from a hygienic String based macro system [1]. Using javac as an API (like jshell does), it seems trivial to come with something like EVAL."\(a) + \(b)" being able to evaluate at runtime any Java expressions. I'm not sure if it's a good thing or a bad thing. R?mi [1] https://en.wikipedia.org/wiki/Hygienic_macro > From: "Jim Laskey" > To: "amber-spec-experts" > Sent: Jeudi 16 Septembre 2021 15:28:41 > Subject: String Tapas Redux: Beyond mere string interpolation > Amber experts, > Now that JDK 17 has been plated and left the kitchen, we should have a look-see > at one of the new menu items Brian and I have had on a slow boil for these last > few months; Templated Strings. > Before you start shouting out, "Templated Strings? This isn't what I ordered! > The subject said string interpolation!!!", take the time to follow this link [ > https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md > | > https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md > ] . After reading, we hope you'll see that the offering is much better than > interpolation meat and potatoes. > Cheers, > -- Jim From brian.goetz at oracle.com Fri Sep 17 00:22:11 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 16 Sep 2021 20:22:11 -0400 Subject: String Tapas Redux: Beyond mere string interpolation In-Reply-To: <1691075592.2270344.1631837416140.JavaMail.zimbra@u-pem.fr> References: <1691075592.2270344.1631837416140.JavaMail.zimbra@u-pem.fr> Message-ID: While I get where you're coming from, I don't think that part is anything new or scary. This is isomorphic to methods like: ??? static Foo EVAL(String script, Object... params) { ... } The slightly more convenient syntax may seem attractive at first, but ultimately, it's not really all that different from the EVAL method above; the difference between ??? EVAL."Foo \{foo} Bar \{bar}" and ??? EVAL("Foo {} Bar {}", foo, bar) is not the difference between "macros" and "no macros". I am sure we'll see interesting abuses, but that's not the point. So, what is the point?? The point is that string interpolation is one of the most commonly requested features, and we'd make a lot of people happy if we did it.? While it's a harmless-seeming feature in theory, we're unwilling to do the weak version of string interpolation that most other languages have satisfied themselves with (we can justify this unwillingness solely on the SQL injection risk, but there are other reasons.)? So if we want to make all those people happy, we have to do a version that is smart enough to address the obvious security hazards. This proposal seems to me about as close as we're going to get to making the people happy while addressing the required constraints, without doing something totally crazy. On 9/16/2021 8:10 PM, Remi Forax wrote: > Brian explicitly ask me to not talk about invokedynamic so i will not > say that there is already an existing protocol between invokedynamic > and a user defined implementation, it's the bootstrap method, > > Let's talk about the elephant in the room: macro. > Templated Strings as currently defined is indiscernible from a > hygienic String based macro system [1]. > > Using javac as an API (like jshell does), it seems trivial to come > with something like EVAL."\(a) + \(b)" being able to evaluate at > runtime any Java expressions. > > I'm not sure if it's a good thing or a bad thing. > > R?mi > > [1] https://en.wikipedia.org/wiki/Hygienic_macro > > > ------------------------------------------------------------------------ > > *From: *"Jim Laskey" > *To: *"amber-spec-experts" > *Sent: *Jeudi 16 Septembre 2021 15:28:41 > *Subject: *String Tapas Redux: Beyond mere string interpolation > > Amber experts, > > Now that JDK 17 has been plated and left the kitchen, we should > have a look-see at one of the new menu items Brian and I have had > on a slow boil for these last few months; Templated Strings. > > Before you start shouting out, "Templated Strings? This isn't what > I ordered! The subject said string interpolation!!!", take the > time to follow this link > https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md > . > After reading, we hope you'll see that the offering is much better > than interpolation meat and potatoes. > > Cheers, > > -- Jim > From amaembo at gmail.com Fri Sep 17 02:35:08 2021 From: amaembo at gmail.com (Tagir Valeev) Date: Fri, 17 Sep 2021 09:35:08 +0700 Subject: String Tapas Redux: Beyond mere string interpolation In-Reply-To: References: Message-ID: Hello! Just read the proposal. I don't have any useful comments for now. For me, the proposal looks great as is. Go ahead and implement it :D With best regards, Tagir Valeev. On Thu, Sep 16, 2021 at 8:28 PM Jim Laskey wrote: > > Amber experts, > > Now that JDK 17 has been plated and left the kitchen, we should have a look-see at one of the new menu items Brian and I have had on a slow boil for these last few months; Templated Strings. > > Before you start shouting out, "Templated Strings? This isn't what I ordered! The subject said string interpolation!!!", take the time to follow this link https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md. After reading, we hope you'll see that the offering is much better than interpolation meat and potatoes. > > Cheers, > > -- Jim From john.r.rose at oracle.com Fri Sep 17 06:43:29 2021 From: john.r.rose at oracle.com (John Rose) Date: Fri, 17 Sep 2021 06:43:29 +0000 Subject: String Tapas Redux: Beyond mere string interpolation In-Reply-To: References: Message-ID: Yay! I agree with Brian?s response to Remi: Nothing new here regarding eval or ASTs. My favorite wished-for-use-case for templated strings is a grammar where the ?holes? are grammar actions or other configuration points for rules. This paper made me envious for the sake of Java, and also made me think, ?when we get string templates we can try our own shenanigans?: http://www.inf.puc-rio.br/~roberto/lpeg/#grammar http://www.inf.puc-rio.br/~roberto/docs/peg.pdf > We can meet our diverse goals by separating mechanism from > policy. How we introduce parameters into string expressions is > mechanism; how we combine the parameters and the string into a final > result (e.g., concatenation) is policy. One might also say we separate wiring from function. How we introduce a segmented string with holes, and also (typed) expressions to fill those holes, is the wiring. (No function yet: We just observe all those values waiting for us to do something with them.) How we arrange the static structure of those values verges on function, but it?s really just setting up for the moment when we have all the values and can run our function. The function comes in when we have all the values (crucially, the dynamic and typed hole-filling values). At that point it?s really ?only? a method call, but that method contains all the function (the ?policy?). The intermediate step where we derive a recipient for the function call, from the static parts (strings, and also hole types), is a factory method or constructor call, one which creates the receiver object (which is constant for that expression, just like a no-capture lambda). It?s important to separate wiring from function in part because (a) wiring can be fully understood while (b) function is inherently undecidable. So it?s good (for extensibility, universality) when the wiring is really simple and clear, and the transitions into the mysterious function-boxes are also really clear. Also, if we focus on wiring that is as universal as possible, we can do fancy stuff like grammars with functional actions. Otherwise, it?s harder. Also, making the function part ?only a method call? means that whatever resources the language has to make method calls be a good notation apply to templated strings. Also, since the ?wiring part? includes the round-up of the static parts of the template expression, it follows that we can do lots of interesting ?compile time? work in the indy or condy instruction that implements the expression as a whole. I am gently insisting that the types of the ?holes? are part of the template setup code because, after all, that?s what the indy needs anyway, and it seems a shame to make them all be erased to Object and Object[]. One reason ?just use Object? is a missed opportunity: You get lots of boxing. Another, deeper one: Without FI-based target typing that would be provided by a general Java method call, you can?t put poly-expressions (like lambdas) into the holes. For example, a PEG template might take a varargs argument of type (PEGRuleAction? actions) where PEGRuleAction is a FI. (Mixing FIs and other data is a challenge I will delay for now, but it?s under the rubric of ?Varargs 2.0?, allowing methods to capture variable-length yet type-heterogeneous arguments. Think also for Map.of(?) which wants to take pairs of arguments of alternating types. But that?s for later.) I?m fully aware that I will be asking for stuff that is beyond a 1.0-level feature set, but in discussing stuff like this I?m hoping to stake out a path for growth to wider set of use cases that inevitably comes if the meaning of the expression is ?call this method on this compute-once receiver?, as opposed to something more constrained (even if the 1.0 level is constrained). Or, to go back to the ?mechanism vs. policy? formulation, the (very interesting) policy is embodied in the template expression receiver object itself that is built on first use of the expression, and it is also in the (very interesting) method that is called on the object with the typed hole values. The mechanism consists of the rules by which the expression receiver object (TemplatePolicy) is created, and what values are passed to its constructor or factory (just once, lazy), and then (each time the expression is evaluated) how the holes are passed to the object, and via which method. The wiring I?m suggesting starts with a one-time setup operation, which needs to be statically defined (no dynamic argument dependencies). I?m suggesting that the policy provides a factory method which is run once (probably via a condy or indy) per expression. I will use really, really dumb names for the two interfaces that characterize the processing at the two phases, the one-time setup and the each-time evaluation of the template. interface TemplatePolicyPhase1> { P2 setup(List parts, MethodType type); // type takes all the hole arguments and returns S } Note that TemplatePolicyPhase1 is a FI. It is a factory run at expression-link time, and it makes a phase 2 thingy which knows how to execute the expression: interface TemplatePolicyPhase2 { List parts(); //?MethodType type(); S execute(Object? args); } or, maybe better with MHs (with a workaround to invokeWA for arity limits): interface TemplatePolicyPhase2 { List parts(); MethodHandle executor(); default MethodType type() { return executor().type().dropParameterTypes(0,1); } default S execute(Object? args) { return executor().bindTo(this).invokeWithArguments(args); } } The names are deliberately dumb here. The latter one (with the MH) is preferable, because (a) it can reflectively report its exact type and (b) the javac compiler can use the MH to pass the arguments under exactly correct types (not just Object, not varargs). The instance of TemplatePolicyPhase1 is really a witness to a particular policy, as well as a way to execute it. Once you have a TemplatePolicyPhase1 in hand it?s clear how the wiring works and how to set up the indy or condy. public class String { public record SimpleTemplatePolicy(List parts, MethodType type) { implements TemplatePolicyPhase2 { maker() { ?do something here? } } // witness to the policy: public static final TemplatePolicyPhase1 SETUP_TEMPLATE_POLICY = SimpleTemplatePolicy::new; } To bootstrap things, there could be a rule whereby that if a type has a static method or field or nested class of appropriate name and type, the construction method (or other witness value) is wired to the position of the TemplatePolicyPhase1. That method can be called from a condy or indy, the first time the expression is executed. That means the inference finds one TemplatePolicyPhase1 witness per type. Eventually we can enhance this further if we want to define a special kind of ?static constant? expression that can provide (as if through a lazy static variable) a computed constant of some sort. Maybe String or SQLQuery can have several named witnesses available: SQLQuery.NORMAL.?template ??. I think there is a place somewhere in the language for lazy statics, which could provide named access points for such ?witnesses?, as well as do many other jobs. If the phase 2 protocol uses method handles, then we desugar to something like this: // int n; Object w; // var s = String.?hello, \{n} times to you, \{w}?; lazy @Condy static final MethodType MT42 = methodType(String.class, /*n*/ int.class, /*w*/ Object.class); lazy @Condy static final List SL42 = Utils.splitForST(?hello, \0 times to you, \0?); lazy @Condy static final TemplatePolicyPhase1 TP43 = String.SETUP_TEMPLATE_POLICY.setup(SL42, MT42)); lazy @Condy static final MethodHandle MH44 = TP43.maker(); var s = (String) MH44.invokeExact(TP43, n, w); In this desugaring, perhaps in some cases javac can decide on some M and call TP43.M(n,w) instead of materializing the MH44, which might as well be (in most cases) just a wrapper around M. A universal ?all wires exposed? policy-free policy might look like this: public record BasicTemplate (TemplatePolicyPhase2 policy, List arguments) { public record BasicPolicy(List parts, MethodType type) { implements TemplatePolicyPhase2 { BasicTemplate makeTemplate(List arguments) { //assert(validateTypedArgs(type, arguments)) return new BasicTemplate(this, arguments); } MethodHandle maker() { return (?MHs.lookup makeTemplate?).asType(type); } // later on, narrow to a more definitive phase1 policy public > P2 setupAgain(TemplatePolicyPhase1 newPolicy) { return newPolicy.setup(policy.parts(), policy.type()); } } // witness to the policy: public static final TemplatePolicyPhase1 SETUP_TEMPLATE_POLICY = BasicPolicy::new; // later on, narrow to a more definitive policy and execute method: public > S executeAgain(TemplatePolicyPhase1 newPolicyP1) { return policy.setupAgain().execute(arguments()); } } Then if we have this: BasicTemplate tem = BasicTemplate.?hello, \{n} times to you, \{w}?; ?it desugars to: lazy @Condy static final MethodType MT42 = methodType(BasicTemplate.class, /*n*/ int.class, /*w*/ Object.class); lazy @Condy static final List SL42 = Utils.splitForST(?hello, \0 times to you, \0?); lazy @Condy static final TemplatePolicyPhase1 TP43 = BasicTemplate.SETUP_TEMPLATE_POLICY.setup(SL42, MT42); lazy @Condy static final MethodHandle MH44 = TP43.maker(); BasicTemplate tem = (BasicTemplate) MH44.invokeExact(TP43, n, w); Later on, if we want to execute the template as if it had originally be defined as a simple string concatenate, we do: String s = tem.executeAgain(String.TemplatePolicy::new); Or we might run it as an SQL query: SQLQuery q = tem.executeAgain(SQLQuery.TemplatePolicy::new); The BasicTemplate is an existence proof that the wiring and the function, the mechanism and the policy, are completely separate, because you can ?rewire? any and all functions/policies by means of the method ?executeAgain?. (I think the names of the two interfaces should maybe be something like ?LinkTemplateExpression? and ?ExecuteTemplateExpression?.) ? John From amaembo at gmail.com Fri Sep 17 07:15:05 2021 From: amaembo at gmail.com (Tagir Valeev) Date: Fri, 17 Sep 2021 14:15:05 +0700 Subject: String Tapas Redux: Beyond mere string interpolation In-Reply-To: References: Message-ID: Btw people say that Scala provides a similar mechanism using StringContext class: https://www.scala-lang.org/api/2.12.2/scala/StringContext.html In fact, Scala's `s` prefix before interpolated literal is a recipe for interpolation provided by a standard library, not by language. And it's possible to declare your own recipes. Quite similar to our proposal. Also, there's some frustration in Twitter comments regarding the article narrative like "We will do it better than existing languages" (especially because Scala was chosen as an example). Probably, the wording could be better, with more respect to other languages. Of course, this doesn't undermine the suggested feature itself, the feature is great. With best regards, Tagir Valeev. On Fri, Sep 17, 2021 at 9:35 AM Tagir Valeev wrote: > > Hello! > > Just read the proposal. I don't have any useful comments for now. For > me, the proposal looks great as is. Go ahead and implement it :D > > With best regards, > Tagir Valeev. > > On Thu, Sep 16, 2021 at 8:28 PM Jim Laskey wrote: > > > > Amber experts, > > > > Now that JDK 17 has been plated and left the kitchen, we should have a look-see at one of the new menu items Brian and I have had on a slow boil for these last few months; Templated Strings. > > > > Before you start shouting out, "Templated Strings? This isn't what I ordered! The subject said string interpolation!!!", take the time to follow this link https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md. After reading, we hope you'll see that the offering is much better than interpolation meat and potatoes. > > > > Cheers, > > > > -- Jim From amaembo at gmail.com Fri Sep 17 10:38:05 2021 From: amaembo at gmail.com (Tagir Valeev) Date: Fri, 17 Sep 2021 17:38:05 +0700 Subject: String Tapas Redux: Beyond mere string interpolation In-Reply-To: References: Message-ID: As for custom template processing in JavaScript, see "Tagged templates" section here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals On Fri, Sep 17, 2021 at 2:15 PM Tagir Valeev wrote: > > Btw people say that Scala provides a similar mechanism using > StringContext class: > https://www.scala-lang.org/api/2.12.2/scala/StringContext.html > In fact, Scala's `s` prefix before interpolated literal is a recipe > for interpolation provided by a standard library, not by language. And > it's possible to declare your own recipes. Quite similar to our > proposal. > > Also, there's some frustration in Twitter comments regarding the > article narrative like "We will do it better than existing languages" > (especially because Scala was chosen as an example). Probably, the > wording could be better, with more respect to other languages. Of > course, this doesn't undermine the suggested feature itself, the > feature is great. > > With best regards, > Tagir Valeev. > > On Fri, Sep 17, 2021 at 9:35 AM Tagir Valeev wrote: > > > > Hello! > > > > Just read the proposal. I don't have any useful comments for now. For > > me, the proposal looks great as is. Go ahead and implement it :D > > > > With best regards, > > Tagir Valeev. > > > > On Thu, Sep 16, 2021 at 8:28 PM Jim Laskey wrote: > > > > > > Amber experts, > > > > > > Now that JDK 17 has been plated and left the kitchen, we should have a look-see at one of the new menu items Brian and I have had on a slow boil for these last few months; Templated Strings. > > > > > > Before you start shouting out, "Templated Strings? This isn't what I ordered! The subject said string interpolation!!!", take the time to follow this link https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md. After reading, we hope you'll see that the offering is much better than interpolation meat and potatoes. > > > > > > Cheers, > > > > > > -- Jim From amaembo at gmail.com Fri Sep 17 10:49:30 2021 From: amaembo at gmail.com (Tagir Valeev) Date: Fri, 17 Sep 2021 17:49:30 +0700 Subject: String Tapas Redux: Beyond mere string interpolation In-Reply-To: References: Message-ID: It's interesting that in both Scala and JavaScript when you define custom interpolation policy, you get a collection of Strings instead of a single string. So in the article, we see "error: file \{} not found" but in Scala/JS you would get List.of("error: file ", " not found")). While for localization, having a single string already baked might be beneficial, I think in the general case, having separate parts may be helpful for interpolation implementations, as no additional parsing (search for \{}) would be necessary. Also, the placeholder designation with \{} (or whatever else) is quite arbitrary. If we just provide a list, we would not need to make this choice. And probably the interop between JVM languages would be easier (of course, Scala/Kotlin folks would like to use template policies defined in Java, and vice versa). Another alternative is to have a single list of template fragments, something like this: sealed interface TemplateFragment { interface StringFragment extends TemplateFragment { String content(); } interface ExpressionFragment extends TemplateFragment { Object content(); } } Now, TemplatedString is just a List of TemplateFragments (it's not always necessary that ExpressionFragments should be interleaved with StringFragments, you may have two ExpressionFragments next to each other). On Fri, Sep 17, 2021 at 5:38 PM Tagir Valeev wrote: > > As for custom template processing in JavaScript, see "Tagged > templates" section here: > https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals > > On Fri, Sep 17, 2021 at 2:15 PM Tagir Valeev wrote: > > > > Btw people say that Scala provides a similar mechanism using > > StringContext class: > > https://www.scala-lang.org/api/2.12.2/scala/StringContext.html > > In fact, Scala's `s` prefix before interpolated literal is a recipe > > for interpolation provided by a standard library, not by language. And > > it's possible to declare your own recipes. Quite similar to our > > proposal. > > > > Also, there's some frustration in Twitter comments regarding the > > article narrative like "We will do it better than existing languages" > > (especially because Scala was chosen as an example). Probably, the > > wording could be better, with more respect to other languages. Of > > course, this doesn't undermine the suggested feature itself, the > > feature is great. > > > > With best regards, > > Tagir Valeev. > > > > On Fri, Sep 17, 2021 at 9:35 AM Tagir Valeev wrote: > > > > > > Hello! > > > > > > Just read the proposal. I don't have any useful comments for now. For > > > me, the proposal looks great as is. Go ahead and implement it :D > > > > > > With best regards, > > > Tagir Valeev. > > > > > > On Thu, Sep 16, 2021 at 8:28 PM Jim Laskey wrote: > > > > > > > > Amber experts, > > > > > > > > Now that JDK 17 has been plated and left the kitchen, we should have a look-see at one of the new menu items Brian and I have had on a slow boil for these last few months; Templated Strings. > > > > > > > > Before you start shouting out, "Templated Strings? This isn't what I ordered! The subject said string interpolation!!!", take the time to follow this link https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md. After reading, we hope you'll see that the offering is much better than interpolation meat and potatoes. > > > > > > > > Cheers, > > > > > > > > -- Jim From james.laskey at oracle.com Fri Sep 17 12:46:34 2021 From: james.laskey at oracle.com (Jim Laskey) Date: Fri, 17 Sep 2021 12:46:34 +0000 Subject: [External] : Re: String Tapas Redux: Beyond mere string interpolation In-Reply-To: References: Message-ID: <705EA71C-821B-40C7-9349-82F3A6B0788B@oracle.com> Agree. Most of the time fragments are what are required. There are also circumstances where having the full string is also useful. As you point, in the doc, we discuss localization, where we use the interpolation string with placeholders as the key to a "ResourceBundle" lookup. During evaluation, we had the TemplatedString object provide access to both string and list of fragments. This was done in a way to share these immutable entities across all TemplatedString instances derived from a call site. No recomputation required. You should also note we defined TemplatedString as an interface. This gives us a great deal of flexibility on how we resolve queries against an instance. So, interop could be simple as deriving from TemplatedString. Cheers, -- Jim > On Sep 17, 2021, at 7:49 AM, Tagir Valeev wrote: > > It's interesting that in both Scala and JavaScript when you define > custom interpolation policy, you get a collection of Strings instead > of a single string. So in the article, we see "error: file \{} not > found" but in Scala/JS you would get List.of("error: file ", " not > found")). While for localization, having a single string already baked > might be beneficial, I think in the general case, having separate > parts may be helpful for interpolation implementations, as no > additional parsing (search for \{}) would be necessary. Also, the > placeholder designation with \{} (or whatever else) is quite > arbitrary. If we just provide a list, we would not need to make this > choice. And probably the interop between JVM languages would be easier > (of course, Scala/Kotlin folks would like to use template policies > defined in Java, and vice versa). > > Another alternative is to have a single list of template fragments, > something like this: > > sealed interface TemplateFragment { > interface StringFragment extends TemplateFragment { > String content(); > } > interface ExpressionFragment extends TemplateFragment { > Object content(); > } > } > > Now, TemplatedString is just a List of TemplateFragments (it's not > always necessary that ExpressionFragments should be interleaved with > StringFragments, you may have two ExpressionFragments next to each > other). > > > On Fri, Sep 17, 2021 at 5:38 PM Tagir Valeev wrote: >> >> As for custom template processing in JavaScript, see "Tagged >> templates" section here: >> https://urldefense.com/v3/__https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals__;!!ACWV5N9M2RV99hQ!acICf3vYAnsi0s35BvYgUPE_tc2ViXGyTrMFkyB6XIkTUclqwEzoTFz4CDmEe-OcUA$ >> >> On Fri, Sep 17, 2021 at 2:15 PM Tagir Valeev wrote: >>> >>> Btw people say that Scala provides a similar mechanism using >>> StringContext class: >>> https://urldefense.com/v3/__https://www.scala-lang.org/api/2.12.2/scala/StringContext.html__;!!ACWV5N9M2RV99hQ!acICf3vYAnsi0s35BvYgUPE_tc2ViXGyTrMFkyB6XIkTUclqwEzoTFz4CDlPJSYXLQ$ >>> In fact, Scala's `s` prefix before interpolated literal is a recipe >>> for interpolation provided by a standard library, not by language. And >>> it's possible to declare your own recipes. Quite similar to our >>> proposal. >>> >>> Also, there's some frustration in Twitter comments regarding the >>> article narrative like "We will do it better than existing languages" >>> (especially because Scala was chosen as an example). Probably, the >>> wording could be better, with more respect to other languages. Of >>> course, this doesn't undermine the suggested feature itself, the >>> feature is great. >>> >>> With best regards, >>> Tagir Valeev. >>> >>> On Fri, Sep 17, 2021 at 9:35 AM Tagir Valeev wrote: >>>> >>>> Hello! >>>> >>>> Just read the proposal. I don't have any useful comments for now. For >>>> me, the proposal looks great as is. Go ahead and implement it :D >>>> >>>> With best regards, >>>> Tagir Valeev. >>>> >>>> On Thu, Sep 16, 2021 at 8:28 PM Jim Laskey wrote: >>>>> >>>>> Amber experts, >>>>> >>>>> Now that JDK 17 has been plated and left the kitchen, we should have a look-see at one of the new menu items Brian and I have had on a slow boil for these last few months; Templated Strings. >>>>> >>>>> Before you start shouting out, "Templated Strings? This isn't what I ordered! The subject said string interpolation!!!", take the time to follow this link https://urldefense.com/v3/__https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md__;!!ACWV5N9M2RV99hQ!acICf3vYAnsi0s35BvYgUPE_tc2ViXGyTrMFkyB6XIkTUclqwEzoTFz4CDmrNWsfyg$ . After reading, we hope you'll see that the offering is much better than interpolation meat and potatoes. >>>>> >>>>> Cheers, >>>>> >>>>> -- Jim From brian.goetz at oracle.com Fri Sep 17 12:50:21 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 17 Sep 2021 08:50:21 -0400 Subject: String Tapas Redux: Beyond mere string interpolation In-Reply-To: References: Message-ID: We went back and forth a few times on this, and might go back and forth some more.? For many applications, the segments are valuable, though for some, the full string is more useful. Whether the framework or the policy object does String::split isn't really that interesting; the real question is how to avoid doing it on every invocation.? There's a story for callsite caching of this and more, but we'd like to nail down the basic programming model first. On 9/17/2021 6:49 AM, Tagir Valeev wrote: > It's interesting that in both Scala and JavaScript when you define > custom interpolation policy, you get a collection of Strings instead > of a single string. So in the article, we see "error: file \{} not > found" but in Scala/JS you would get List.of("error: file ", " not > found")). While for localization, having a single string already baked > might be beneficial, I think in the general case, having separate > parts may be helpful for interpolation implementations, as no > additional parsing (search for \{}) would be necessary. Also, the > placeholder designation with \{} (or whatever else) is quite > arbitrary. If we just provide a list, we would not need to make this > choice. And probably the interop between JVM languages would be easier > (of course, Scala/Kotlin folks would like to use template policies > defined in Java, and vice versa). > > Another alternative is to have a single list of template fragments, > something like this: > > sealed interface TemplateFragment { > interface StringFragment extends TemplateFragment { > String content(); > } > interface ExpressionFragment extends TemplateFragment { > Object content(); > } > } > > Now, TemplatedString is just a List of TemplateFragments (it's not > always necessary that ExpressionFragments should be interleaved with > StringFragments, you may have two ExpressionFragments next to each > other). > > > On Fri, Sep 17, 2021 at 5:38 PM Tagir Valeev wrote: >> As for custom template processing in JavaScript, see "Tagged >> templates" section here: >> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals >> >> On Fri, Sep 17, 2021 at 2:15 PM Tagir Valeev wrote: >>> Btw people say that Scala provides a similar mechanism using >>> StringContext class: >>> https://www.scala-lang.org/api/2.12.2/scala/StringContext.html >>> In fact, Scala's `s` prefix before interpolated literal is a recipe >>> for interpolation provided by a standard library, not by language. And >>> it's possible to declare your own recipes. Quite similar to our >>> proposal. >>> >>> Also, there's some frustration in Twitter comments regarding the >>> article narrative like "We will do it better than existing languages" >>> (especially because Scala was chosen as an example). Probably, the >>> wording could be better, with more respect to other languages. Of >>> course, this doesn't undermine the suggested feature itself, the >>> feature is great. >>> >>> With best regards, >>> Tagir Valeev. >>> >>> On Fri, Sep 17, 2021 at 9:35 AM Tagir Valeev wrote: >>>> Hello! >>>> >>>> Just read the proposal. I don't have any useful comments for now. For >>>> me, the proposal looks great as is. Go ahead and implement it :D >>>> >>>> With best regards, >>>> Tagir Valeev. >>>> >>>> On Thu, Sep 16, 2021 at 8:28 PM Jim Laskey wrote: >>>>> Amber experts, >>>>> >>>>> Now that JDK 17 has been plated and left the kitchen, we should have a look-see at one of the new menu items Brian and I have had on a slow boil for these last few months; Templated Strings. >>>>> >>>>> Before you start shouting out, "Templated Strings? This isn't what I ordered! The subject said string interpolation!!!", take the time to follow this link https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md. After reading, we hope you'll see that the offering is much better than interpolation meat and potatoes. >>>>> >>>>> Cheers, >>>>> >>>>> -- Jim From forax at univ-mlv.fr Fri Sep 17 15:10:01 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 17 Sep 2021 17:10:01 +0200 (CEST) Subject: String Tapas Redux: Beyond mere string interpolation In-Reply-To: References: Message-ID: <385297919.2657263.1631891401986.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "John Rose" > To: "Jim Laskey" > Cc: "amber-spec-experts" > Sent: Vendredi 17 Septembre 2021 08:43:29 > Subject: Re: String Tapas Redux: Beyond mere string interpolation > Yay! > > I agree with Brian?s response to Remi: Nothing new here > regarding eval or ASTs. Ok ! > > My favorite wished-for-use-case for templated strings is > a grammar where the ?holes? are grammar actions or > other configuration points for rules. This paper made > me envious for the sake of Java, and also made me think, > ?when we get string templates we can try our own > shenanigans?: > > http://www.inf.puc-rio.br/~roberto/lpeg/#grammar > http://www.inf.puc-rio.br/~roberto/docs/peg.pdf > >> We can meet our diverse goals by separating mechanism from >> policy. How we introduce parameters into string expressions is >> mechanism; how we combine the parameters and the string into a final >> result (e.g., concatenation) is policy. > > One might also say we separate wiring from function. How we introduce > a segmented string with holes, and also (typed) expressions to fill > those holes, is the wiring. (No function yet: We just observe all > those values waiting for us to do something with them.) How we > arrange the static structure of those values verges on function, but > it?s really just setting up for the moment when we have all the values > and can run our function. The function comes in when we have all the > values (crucially, the dynamic and typed hole-filling values). At > that point it?s really ?only? a method call, but that method contains > all the function (the ?policy?). The intermediate step where we > derive a recipient for the function call, from the static parts > (strings, and also hole types), is a factory method or constructor > call, one which creates the receiver object (which is constant for > that expression, just like a no-capture lambda). yes, it's a two steps process, first return a method handle (as you say below) from the formatted String, then call it with the arguments. Hum, it's really, really like a bootstrap method, no ? And we already know that because this is how the StringConcatFactory works, the only difference is that the format used by the StringConcatFactory define a hole as a special unicode character where here we want to have named holes (for the ResourceBundle example). > > It?s important to separate wiring from function in part because > (a) wiring can be fully understood while (b) function is inherently > undecidable. So it?s good (for extensibility, universality) when > the wiring is really simple and clear, and the transitions into > the mysterious function-boxes are also really clear. yes, > > Also, if we focus on wiring that is as universal as possible, > we can do fancy stuff like grammars with functional actions. > Otherwise, it?s harder. > > Also, making the function part ?only a method call? means > that whatever resources the language has to make method calls > be a good notation apply to templated strings. > > Also, since the ?wiring part? includes the round-up of the > static parts of the template expression, it follows that we can > do lots of interesting ?compile time? work in the indy or condy > instruction that implements the expression as a whole. amen > > I am gently insisting that the types of the ?holes? are part > of the template setup code because, after all, that?s what the > indy needs anyway, and it seems a shame to make them all > be erased to Object and Object[]. or String[] like in Scala. > > One reason ?just use Object? is a missed opportunity: You > get lots of boxing. Another, deeper one: Without FI-based > target typing that would be provided by a general Java method > call, you can?t put poly-expressions (like lambdas) into the holes. The other issue is checked exception, we need a way to allow the possible checked exception to be thrown (i have no idea how ?). > > For example, a PEG template might take a varargs argument of > type (PEGRuleAction? actions) where PEGRuleAction is a FI. > (Mixing FIs and other data is a challenge I will delay for now, > but it?s under the rubric of ?Varargs 2.0?, allowing methods > to capture variable-length yet type-heterogeneous arguments. > Think also for Map.of(?) which wants to take pairs > of arguments of alternating types. But that?s for later.) In my fantasy world, Record** is the type you are looking for, it's how you can declare a keyword arguments that that should be generated as a synthetic record by the compiler. void foo(Record** r) ask for a record generated by the compiler so the MethodType passed to the bootstrap method will be something like ($SyntheticRecord$)V > > I?m fully aware that I will be asking for stuff that is beyond > a 1.0-level feature set, but in discussing stuff like this I?m > hoping to stake out a path for growth to wider set of use > cases that inevitably comes if the meaning of the expression > is ?call this method on this compute-once receiver?, as opposed > to something more constrained (even if the 1.0 level is constrained). > > Or, to go back to the ?mechanism vs. policy? formulation, > the (very interesting) policy is embodied in the template > expression receiver object itself that is built on first use > of the expression, and it is also in the (very interesting) > method that is called on the object with the typed hole > values. The mechanism consists of the rules by which > the expression receiver object (TemplatePolicy) is created, > and what values are passed to its constructor or factory > (just once, lazy), and then (each time the expression is > evaluated) how the holes are passed to the object, and > via which method. > > The wiring I?m suggesting starts with a one-time setup > operation, which needs to be statically defined (no dynamic > argument dependencies). I?m suggesting that the policy > provides a factory method which is run once (probably > via a condy or indy) per expression. [...] Let me summarize what i'm thinking of: 3) at the end, calling a String Template is like calling a MethodHandle 2) we need a one time wiring in a very similar way to how a boostrap method is called by invokedynamic one of the boostrap arguments is the String representing the Template String 1) unlike a bootstrap method, we want to define all possibles signatures for the compiler so the compiler will not accept all String template but the one - with a peculiar receiver (an instance or a class ?) - with some constraints on the types of the parameters like PEGRuleAction... - we should be able to declare the possible checked exceptions We already know how to do (2) and (3) because it's very similar to the StringConcatFactory. (1) is more complex because it's how we should interact with the compiler so we reject some Template String at compile time, and i believe that here we don't want the compiler to execute any code so it has to done in a declarative way (in a similar way the meta-annotation @Target restricts where you can put an annotation in a declarative way so can be interpreted by the compiler without running any user code). Here is my proposal. (1) there is a new keyword __template-string__ that can be used on instance or static method, this method is like an abstract method because it has no code, but is used by the compiler to typecheck the Template String (2) if there is a method declared __template-string__ in the class, the class must also declare a boostrap method that works the same way as a classic bootstrap method and takes the template as the last parameter (as a String to keep things simple for now) (3) any occurrences of a Template String that reference a class containing a method __template-string__ is desugared as an invokedynamic that calls the bootstrap method and takes the parameter of the Template String as regular arguments. Some examples below: --- With the Template String var name = ... var age = ... var s = String."Hello, \(name), I am \(age) years old"; // "\(name)" is a shortcut for String."\(name)" The class j.l.String declares: package java.lang.String; public class String { public static __template-string__ String interpolate(Object... args); public static CallSite bootstrap(Lookup lookup, String name, MethodType desc, String template) { String stringConcatTemplate = escapeHoleAsStringConcatFactoryHoles(template); return StringConcatFactory.boostrap(lookup, name, desc, stringConcatTemplate); } } --- With the Template String (with no parameters) var matcher = Pattern."[A-Za-z]+".matcher(text); The class j.u.regex.Pattern declares: package java.util.regex; public class Pattern { public static Pattern compile(String regex) { ... } public static __template-string__ Pattern interpolate(); public static CallSite bootstrap(Lookup lookup, String name, MethodType desc, String template) { return new ConstantCallSite(MethodHandles.constant(Pattern.class, Pattern.compile(template))); } } --- With the Template String, var connection = ... var table = ... try(var resultSet = connection."SELECT * FROM \(table)") { ... } package java.sql; public class Connection { public __template-string__ ResultSet interpolate(Object... args) throws IOEception; public static CallSite bootstrap(Lookup lookup, String name, MethodType desc, String template) { String queryText = convertTemplateToSQLQuery(template); return new ConstantCallSite(...); } } > > ? John R?mi From forax at univ-mlv.fr Fri Sep 17 15:40:43 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 17 Sep 2021 17:40:43 +0200 (CEST) Subject: String Tapas Redux: Beyond mere string interpolation In-Reply-To: References: Message-ID: <1660371667.2672880.1631893243452.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Tagir Valeev" > To: "Jim Laskey" > Cc: "amber-spec-experts" > Sent: Vendredi 17 Septembre 2021 12:49:30 > Subject: Re: String Tapas Redux: Beyond mere string interpolation > It's interesting that in both Scala and JavaScript when you define > custom interpolation policy, you get a collection of Strings instead > of a single string. So in the article, we see "error: file \{} not > found" but in Scala/JS you would get List.of("error: file ", " not > found")). While for localization, having a single string already baked > might be beneficial, I think in the general case, having separate > parts may be helpful for interpolation implementations, as no > additional parsing (search for \{}) would be necessary. Also, the > placeholder designation with \{} (or whatever else) is quite > arbitrary. As John says, the parsing will likely be done once. And for the implementation, to avoid any handcoded bug riddled code, we can provide an iterator or something like that take care of the parsing and making it representation agnostic. [...] R?mi > > > On Fri, Sep 17, 2021 at 5:38 PM Tagir Valeev wrote: >> >> As for custom template processing in JavaScript, see "Tagged >> templates" section here: >> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals >> >> On Fri, Sep 17, 2021 at 2:15 PM Tagir Valeev wrote: >> > >> > Btw people say that Scala provides a similar mechanism using >> > StringContext class: >> > https://www.scala-lang.org/api/2.12.2/scala/StringContext.html >> > In fact, Scala's `s` prefix before interpolated literal is a recipe >> > for interpolation provided by a standard library, not by language. And >> > it's possible to declare your own recipes. Quite similar to our >> > proposal. >> > >> > Also, there's some frustration in Twitter comments regarding the >> > article narrative like "We will do it better than existing languages" >> > (especially because Scala was chosen as an example). Probably, the >> > wording could be better, with more respect to other languages. Of >> > course, this doesn't undermine the suggested feature itself, the >> > feature is great. >> > >> > With best regards, >> > Tagir Valeev. >> > >> > On Fri, Sep 17, 2021 at 9:35 AM Tagir Valeev wrote: >> > > >> > > Hello! >> > > >> > > Just read the proposal. I don't have any useful comments for now. For >> > > me, the proposal looks great as is. Go ahead and implement it :D >> > > >> > > With best regards, >> > > Tagir Valeev. >> > > >> > > On Thu, Sep 16, 2021 at 8:28 PM Jim Laskey wrote: >> > > > >> > > > Amber experts, >> > > > >> > > > Now that JDK 17 has been plated and left the kitchen, we should have a look-see >> > > > at one of the new menu items Brian and I have had on a slow boil for these last >> > > > few months; Templated Strings. >> > > > >> > > > Before you start shouting out, "Templated Strings? This isn't what I ordered! >> > > > The subject said string interpolation!!!", take the time to follow this link >> > > > https://github.com/openjdk/amber-docs/tree/master/site/design-notes/templated-strings.md. >> > > > After reading, we hope you'll see that the offering is much better than >> > > > interpolation meat and potatoes. >> > > > >> > > > Cheers, >> > > > > > > > > -- Jim From john.r.rose at oracle.com Fri Sep 17 22:25:19 2021 From: john.r.rose at oracle.com (John Rose) Date: Fri, 17 Sep 2021 22:25:19 +0000 Subject: String Tapas Redux: Beyond mere string interpolation In-Reply-To: References: Message-ID: <35068DE0-4634-4C79-A3A2-2A2B7E232928@oracle.com> On Sep 17, 2021, at 3:49 AM, Tagir Valeev > wrote: Another alternative is to have a single list of template fragments, something like this: Probably the VM will prefer to see a single string. There are lots of unused Unicode points that can serve as segment markers. This of course only directly affects the translation strategy, not the user-visible APIs, except the lowest level BSM components. Condy can make up any representational differences to List or something more exotic. Also, languages like Scala which want to add modifiers to interpolation points, like \{PI}%.3f, can share add their own parsing logic to the single long string, just as easily (or more easily) as to a list of strings to be individually parsed. Basically, if it?s just one long string, then you take your domain-specific parser and make sure it properly lexes the ?object replacement marker? provided by unicode, as well as whatever string structure you also want. From forax at univ-mlv.fr Fri Sep 24 09:45:27 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 24 Sep 2021 11:45:27 +0200 (CEST) Subject: Sealed Exception In-Reply-To: References: Message-ID: <287949520.2481186.1632476727753.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Zheka Kozlov" > To: "amber-dev" > Sent: Vendredi 24 Septembre 2021 10:30:54 > Subject: Sealed Exception > Hi! CC amber-spec-experts > > Java 17 compiler forces me to insert an unreachable catch block for the > base Exception: > > public static void main(String[] args) { > try { > f(); > } catch (Ex1 e) { > e.printStackTrace(); > } catch (Ex2 e) { > e.printStackTrace(); > } catch (BaseEx e) { > // Unreachable > } > } > > private static void f() throws BaseEx { > } > > sealed abstract class BaseEx extends Exception permits Ex1, Ex2 { > } > > Otherwise it doesn't compile. Was this decision intentional? I don't think so, it's something we have overlooked. > If yes, why? If not, can we fix it? I see this as an unfortunate limitation. I agree, it should be fixed. > > With best regards, Zheka Kozlov. Regards, R?mi From amaembo at gmail.com Fri Sep 24 11:40:21 2021 From: amaembo at gmail.com (Tagir Valeev) Date: Fri, 24 Sep 2021 18:40:21 +0700 Subject: Sealed Exception In-Reply-To: <287949520.2481186.1632476727753.JavaMail.zimbra@u-pem.fr> References: <287949520.2481186.1632476727753.JavaMail.zimbra@u-pem.fr> Message-ID: Agreed. Looks like this case was just overlooked. Abstract exception class is quite an unusual thing but probably it will be more useful with sealed classes. With best regards, Tagir Valeev. On Fri, Sep 24, 2021 at 4:45 PM Remi Forax wrote: > ----- Original Message ----- > > From: "Zheka Kozlov" > > To: "amber-dev" > > Sent: Vendredi 24 Septembre 2021 10:30:54 > > Subject: Sealed Exception > > > Hi! > > CC amber-spec-experts > > > > > Java 17 compiler forces me to insert an unreachable catch block for the > > base Exception: > > > > public static void main(String[] args) { > > try { > > f(); > > } catch (Ex1 e) { > > e.printStackTrace(); > > } catch (Ex2 e) { > > e.printStackTrace(); > > } catch (BaseEx e) { > > // Unreachable > > } > > } > > > > private static void f() throws BaseEx { > > } > > > > sealed abstract class BaseEx extends Exception permits Ex1, Ex2 { > > } > > > > Otherwise it doesn't compile. Was this decision intentional? > > I don't think so, it's something we have overlooked. > > > If yes, why? If not, can we fix it? I see this as an unfortunate > limitation. > > I agree, it should be fixed. > > > > > With best regards, Zheka Kozlov. > > Regards, > R?mi > > From brian.goetz at oracle.com Fri Sep 24 16:55:01 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 24 Sep 2021 16:55:01 +0000 Subject: Sealed Exception In-Reply-To: References: <287949520.2481186.1632476727753.JavaMail.zimbra@u-pem.fr> Message-ID: <62A7C84B-16D2-4F7B-80CD-41C875EE9BCA@oracle.com> I agree that this was just something we overlooked, though not necessarily with the leap from there to ?so of course we should fix it?; the incremental return-on-complexity there is probably quite low, unless the spec fix is truly trivial. As Tagir points out, this is at least currently something quite rare. > On Sep 24, 2021, at 7:40 AM, Tagir Valeev wrote: > > Agreed. Looks like this case was just overlooked. Abstract exception class > is quite an unusual thing but probably it will be more useful with sealed > classes. > > With best regards, > Tagir Valeev. > > On Fri, Sep 24, 2021 at 4:45 PM Remi Forax wrote: > >> ----- Original Message ----- >>> From: "Zheka Kozlov" >>> To: "amber-dev" >>> Sent: Vendredi 24 Septembre 2021 10:30:54 >>> Subject: Sealed Exception >> >>> Hi! >> >> CC amber-spec-experts >> >>> >>> Java 17 compiler forces me to insert an unreachable catch block for the >>> base Exception: >>> >>> public static void main(String[] args) { >>> try { >>> f(); >>> } catch (Ex1 e) { >>> e.printStackTrace(); >>> } catch (Ex2 e) { >>> e.printStackTrace(); >>> } catch (BaseEx e) { >>> // Unreachable >>> } >>> } >>> >>> private static void f() throws BaseEx { >>> } >>> >>> sealed abstract class BaseEx extends Exception permits Ex1, Ex2 { >>> } >>> >>> Otherwise it doesn't compile. Was this decision intentional? >> >> I don't think so, it's something we have overlooked. >> >>> If yes, why? If not, can we fix it? I see this as an unfortunate >> limitation. >> >> I agree, it should be fixed. >> >>> >>> With best regards, Zheka Kozlov. >> >> Regards, >> R?mi >> >> From forax at univ-mlv.fr Sat Sep 25 06:57:45 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sat, 25 Sep 2021 08:57:45 +0200 (CEST) Subject: Sealed Exception In-Reply-To: <62A7C84B-16D2-4F7B-80CD-41C875EE9BCA@oracle.com> References: <287949520.2481186.1632476727753.JavaMail.zimbra@u-pem.fr> <62A7C84B-16D2-4F7B-80CD-41C875EE9BCA@oracle.com> Message-ID: <1837672981.3060085.1632553065771.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Brian Goetz" > To: "Tagir Valeev" > Cc: "Remi Forax" , "Zheka Kozlov" , "amber-spec-experts" > > Sent: Vendredi 24 Septembre 2021 18:55:01 > Subject: Re: Sealed Exception > I agree that this was just something we overlooked, though not necessarily with > the leap from there to ?so of course we should fix it?; the incremental > return-on-complexity there is probably quite low, unless the spec fix is truly > trivial. As Tagir points out, this is at least currently something quite rare. Not fixing interaction between features in a language, here exception and sealed classes has a cost in term of how people make their mind around a feature. Having a non consistent story for a feature make people less inclined to use it. Given that sealed classes is something pivotal to the introduction of pattern matching, i think we should cover the last mile in this case. BTW, i don't thing we are very good at the moment at explaining where and why pattern matching should be used. R?mi > >> On Sep 24, 2021, at 7:40 AM, Tagir Valeev wrote: >> >> Agreed. Looks like this case was just overlooked. Abstract exception class >> is quite an unusual thing but probably it will be more useful with sealed >> classes. >> >> With best regards, >> Tagir Valeev. >> >> On Fri, Sep 24, 2021 at 4:45 PM Remi Forax wrote: >> >>> ----- Original Message ----- >>>> From: "Zheka Kozlov" >>>> To: "amber-dev" >>>> Sent: Vendredi 24 Septembre 2021 10:30:54 >>>> Subject: Sealed Exception >>> >>>> Hi! >>> >>> CC amber-spec-experts >>> >>>> >>>> Java 17 compiler forces me to insert an unreachable catch block for the >>>> base Exception: >>>> >>>> public static void main(String[] args) { >>>> try { >>>> f(); >>>> } catch (Ex1 e) { >>>> e.printStackTrace(); >>>> } catch (Ex2 e) { >>>> e.printStackTrace(); >>>> } catch (BaseEx e) { >>>> // Unreachable >>>> } >>>> } >>>> >>>> private static void f() throws BaseEx { >>>> } >>>> >>>> sealed abstract class BaseEx extends Exception permits Ex1, Ex2 { >>>> } >>>> >>>> Otherwise it doesn't compile. Was this decision intentional? >>> >>> I don't think so, it's something we have overlooked. >>> >>>> If yes, why? If not, can we fix it? I see this as an unfortunate >>> limitation. >>> >>> I agree, it should be fixed. >>> >>>> >>>> With best regards, Zheka Kozlov. >>> >>> Regards, >>> R?mi >>> From forax at univ-mlv.fr Sat Sep 25 19:39:42 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Sat, 25 Sep 2021 21:39:42 +0200 (CEST) Subject: Feedback on pattern matching (preview feature) In-Reply-To: <180521c2-8688-a06e-5023-f2ee4c5556d5@oracle.com> References: <180521c2-8688-a06e-5023-f2ee4c5556d5@oracle.com> Message-ID: <1210001934.3138235.1632598782618.JavaMail.zimbra@u-pem.fr> [re-sent to amber-spec-experts instead of amber-dev] ----- Original Message ----- > From: "Brian Goetz" > To: "Vikram Bakshi" , "amber-dev" > Sent: Samedi 25 Septembre 2021 17:28:11 > Subject: Re: Feedback on pattern matching (preview feature) > The example you cite is a peek into a feature not yet implemented (its a > "and beyond" talk), so not only is there no "as patterns", but no > patterns yet for which "as patterns" would be sensible. > > When we have deconstruction patterns as per the example, you'll be able > to provide an optional binding: > > ??? case Point(var x, var y): // don't care about the point > > ??? case Point(var x, var y) p: // bind the point too > > which we believe will cover the need in a less "nailed on the side" way. In that case (pun intended), we may want to make the binding of the type pattern optional too. case Type: // type pattern with no binding case Type variable: // type pattern + binding This look like the dual of '_', instead of asking for a binding, make it optional. If we follow that rabbit hole, then we should be able to write case Point(var, var): R?mi > > On 9/25/2021 10:21 AM, Vikram Bakshi wrote: >> Hello, >> >> I was playing around with the new pattern matching and wondered about the >> current absence of "as-patterns". Are there any plans to bring them to Java >> in a future JEP? >> >> An example use case where they could be useful is demonstrated in the >> recent video from the official Java YouTube channel ( >> https://youtu.be/UlFFKkq6fyU) at 17:22. >> >> >> Regards, > > Vikram From forax at univ-mlv.fr Sun Sep 26 19:11:24 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Sun, 26 Sep 2021 21:11:24 +0200 (CEST) Subject: It should be possible to type a switch expression with void Message-ID: <242527081.3288886.1632683484134.JavaMail.zimbra@u-pem.fr> There is a bad interaction between a lambda and a switch expression, a lambda allows its expression to be typed void but a switch expression can not be typed void, so the following code does not compile sealed interface I permits A, B {} record A() {} record B() {} public Optional findOneA(List list) { return list.stream() .mapMulti((i, consumer) -> switch(i) { case A a -> consumer.accept(a); case B b -> {} }) .findFirst(); } This bug occurs for with any methods that takes a Consumer has parameter (so stream.forEach/peek, Iterator.forEachRemaining etc). The workaound is to add a pair of curly braces around the switch to transform it to a switch statement. For me, it should be possible to have a switch expression typed void. This is a backward compatible change given that the code does not compile otherwise. regards, R?mi From amaembo at gmail.com Mon Sep 27 04:38:14 2021 From: amaembo at gmail.com (Tagir Valeev) Date: Mon, 27 Sep 2021 11:38:14 +0700 Subject: It should be possible to type a switch expression with void In-Reply-To: <242527081.3288886.1632683484134.JavaMail.zimbra@u-pem.fr> References: <242527081.3288886.1632683484134.JavaMail.zimbra@u-pem.fr> Message-ID: If you expect this to be changed, the same would be expected e.g. from ?: expression. This also would create some kind of confusion: the switch expression cannot be inside the expression statement, but the expression statement is mostly associated with void-type expression. Btw, there's another place where void-expression is allowed: update clause in for statement: for(int i=0; i<10; switch(i) {case 1 -> System.out.println(i++);default -> i++;}) {} Should this work? Your example assumes more changes to the switch expression, not just the 'void' type. You also assume that the block can omit the yield statement, and in this case the resulting type of the corresponding rule is considered to be 'void'. Also, what will be the resulting type of switch expression in case if different branches have different void-ness? E.g.: (i, consumer) -> switch(i) { case A a -> consumer.accept(a); // void branch case B b -> myList.add(b); // boolean branch } I don't think we already have result type rules for void & boolean (JLS 15.28.1). Should we set the resulting type as 'void'? This might be confusing and error-prone. E.g., I have two method overloads (think of ExecutorService.submit(Runnable) and ExecutorService.submit(Callable)). I may assume that the result is used but in fact it's not because I forget yield in one of the branches, so the rule type is set to void, this changes the type of the whole switch expression to the 'void', so the results of all other branches are ignored, lambda becomes void-compatible and we resolve to submit(Runnable) instead of submit(Callable). Sounds not very good. With best regards, Tagir Valeev. On Mon, Sep 27, 2021 at 2:11 AM Remi Forax wrote: > > There is a bad interaction between a lambda and a switch expression, > a lambda allows its expression to be typed void but a switch expression can not be typed void, > so the following code does not compile > > sealed interface I permits A, B {} > record A() {} > record B() {} > > public Optional findOneA(List list) { > return list.stream() > .mapMulti((i, consumer) -> switch(i) { > case A a -> consumer.accept(a); > case B b -> {} > }) > .findFirst(); > } > > This bug occurs for with any methods that takes a Consumer has parameter (so stream.forEach/peek, Iterator.forEachRemaining etc). > > The workaound is to add a pair of curly braces around the switch to transform it to a switch statement. > > For me, it should be possible to have a switch expression typed void. This is a backward compatible change given that the code does not compile otherwise. > > regards, > R?mi From brian.goetz at oracle.com Mon Sep 27 15:07:06 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 27 Sep 2021 11:07:06 -0400 Subject: It should be possible to type a switch expression with void In-Reply-To: <242527081.3288886.1632683484134.JavaMail.zimbra@u-pem.fr> References: <242527081.3288886.1632683484134.JavaMail.zimbra@u-pem.fr> Message-ID: This is part of a larger problem, and I don't think point fixes are really the answer here.? For this reason (and others), any sort of "type more things as void" is on hold for now.? Here's a sketch of the motivation, which comes from Valhalla. A big part of Valhalla is unifying primitives and references. This means (yay) that we can say goodbye to IntPredicate and friends, since we can have: ??? interface IntPredicate extends Predicate { ... } In turn, we can also start to unify Predicate into Function: ??? interface Predicate extends Function { ... } But Consumer is a roadblock, because void is not a type.? We'd like to have a path to getting to this unification; tinkering with treating more statements as void expressions could make this harder. On 9/26/2021 3:11 PM, Remi Forax wrote: > There is a bad interaction between a lambda and a switch expression, > a lambda allows its expression to be typed void but a switch expression can not be typed void, > so the following code does not compile > > sealed interface I permits A, B {} > record A() {} > record B() {} > > public Optional findOneA(List list) { > return list.stream() > .mapMulti((i, consumer) -> switch(i) { > case A a -> consumer.accept(a); > case B b -> {} > }) > .findFirst(); > } > > This bug occurs for with any methods that takes a Consumer has parameter (so stream.forEach/peek, Iterator.forEachRemaining etc). > > The workaound is to add a pair of curly braces around the switch to transform it to a switch statement. > > For me, it should be possible to have a switch expression typed void. This is a backward compatible change given that the code does not compile otherwise. > > regards, > R?mi From forax at univ-mlv.fr Mon Sep 27 19:33:14 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Mon, 27 Sep 2021 21:33:14 +0200 (CEST) Subject: It should be possible to type a switch expression with void In-Reply-To: References: <242527081.3288886.1632683484134.JavaMail.zimbra@u-pem.fr> Message-ID: <1455346740.510137.1632771194806.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Tagir Valeev" > To: "Remi Forax" > Cc: "amber-spec-experts" > Sent: Lundi 27 Septembre 2021 06:38:14 > Subject: Re: It should be possible to type a switch expression with void > If you expect this to be changed, the same would be expected e.g. from ?: expression. Why ? Both the lambda expression and the switch uses the arrow syntax but the associated semantics is different. A lambda allow void expression. A switch does not allow void expression. It feels arbitrary and obvisouly does not work when you combine the two syntax. ?: does not use the arrow syntax so it does not have to be changed the same way, it does not have to accept a yield. > This also would create some kind of confusion: the > switch expression cannot be inside the expression statement, but the > expression statement is mostly associated with void-type expression. Nobody knows what the expression statement is apart if you know the Java grammar. I don't think it's a real issue. > > Btw, there's another place where void-expression is allowed: update > clause in for statement: > for(int i=0; i<10; switch(i) {case 1 -> > System.out.println(i++);default -> i++;}) {} > Should this work? again, no arrow used here. > > Your example assumes more changes to the switch expression, not just > the 'void' type. You also assume that the block can omit the yield > statement, and in this case the resulting type of the corresponding > rule is considered to be 'void'. Also, what will be the resulting type > of switch expression in case if different branches have different > void-ness? E.g.: > > (i, consumer) -> switch(i) { > case A a -> consumer.accept(a); // void branch > case B b -> myList.add(b); // boolean branch > } > > I don't think we already have result type rules for void & boolean > (JLS 15.28.1). Should we set the resulting type as 'void'? This might > be confusing and error-prone. E.g., I have two method overloads (think > of ExecutorService.submit(Runnable) and > ExecutorService.submit(Callable)). I may assume that the result is > used but in fact it's not because I forget yield in one of the > branches, so the rule type is set to void, this changes the type of > the whole switch expression to the 'void', so the results of all other > branches are ignored, lambda becomes void-compatible and we resolve to > submit(Runnable) instead of submit(Callable). Sounds not very good. The problem with submit(Runnable) vs submit(Callable) is already a problem with a lambda, we have a warning if someone write such overloads, sadly the methods Executor.submit() predates this warning which was introduced in 8. Apart that corner case, yes, it will make the error reporting a little harder, if a switch expression is typed void and the value is stored into a variable, the error can be either there is a missing yield in a case or the value should not be stored in a local variable. But it's very similar to the way a lambda works, if a lambda call a method that return void but the functional interface does not accept void, the error can be either that the body is wrong or that the functional interface type is wrong. > > With best regards, > Tagir Valeev. regards, R?mi > > On Mon, Sep 27, 2021 at 2:11 AM Remi Forax wrote: >> >> There is a bad interaction between a lambda and a switch expression, >> a lambda allows its expression to be typed void but a switch expression can not >> be typed void, >> so the following code does not compile >> >> sealed interface I permits A, B {} >> record A() {} >> record B() {} >> >> public Optional findOneA(List list) { >> return list.stream() >> .mapMulti((i, consumer) -> switch(i) { >> case A a -> consumer.accept(a); >> case B b -> {} >> }) >> .findFirst(); >> } >> >> This bug occurs for with any methods that takes a Consumer has parameter (so >> stream.forEach/peek, Iterator.forEachRemaining etc). >> >> The workaound is to add a pair of curly braces around the switch to transform it >> to a switch statement. >> >> For me, it should be possible to have a switch expression typed void. This is a >> backward compatible change given that the code does not compile otherwise. >> >> regards, > > R?mi From forax at univ-mlv.fr Mon Sep 27 19:36:25 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Mon, 27 Sep 2021 21:36:25 +0200 (CEST) Subject: It should be possible to type a switch expression with void In-Reply-To: References: <242527081.3288886.1632683484134.JavaMail.zimbra@u-pem.fr> Message-ID: <1359822856.510543.1632771385757.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "Remi Forax" , "amber-spec-experts" > > Sent: Lundi 27 Septembre 2021 17:07:06 > Subject: Re: It should be possible to type a switch expression with void > This is part of a larger problem, and I don't think point fixes are really the > answer here. For this reason (and others), any sort of "type more things as > void" is on hold for now. Here's a sketch of the motivation, which comes from > Valhalla. > A big part of Valhalla is unifying primitives and references. This means (yay) > that we can say goodbye to IntPredicate and friends, since we can have: > interface IntPredicate extends Predicate { ... } > In turn, we can also start to unify Predicate into Function: > interface Predicate extends Function { ... } > But Consumer is a roadblock, because void is not a type. We'd like to have a > path to getting to this unification; tinkering with treating more statements as > void expressions could make this harder. This issue is not directly related to a switch expression allowing void expressions or not. If we Function it, the only way to have the void be propagated to a functional interface type is to use a lambda expression, and lambda expressions already allow a void expression. So we will not make that issue worst. This issue already exists. R?mi > On 9/26/2021 3:11 PM, Remi Forax wrote: >> There is a bad interaction between a lambda and a switch expression, >> a lambda allows its expression to be typed void but a switch expression can not >> be typed void, >> so the following code does not compile >> sealed interface I permits A, B {} >> record A() {} >> record B() {} >> public Optional findOneA(List list) { >> return list.stream() >> .mapMulti((i, consumer) -> switch(i) { >> case A a -> consumer.accept(a); >> case B b -> {} >> }) >> .findFirst(); >> } >> This bug occurs for with any methods that takes a Consumer has parameter (so >> stream.forEach/peek, Iterator.forEachRemaining etc). >> The workaound is to add a pair of curly braces around the switch to transform it >> to a switch statement. >> For me, it should be possible to have a switch expression typed void. This is a >> backward compatible change given that the code does not compile otherwise. >> regards, >> R?mi From brian.goetz at oracle.com Thu Sep 30 22:25:26 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 30 Sep 2021 18:25:26 -0400 Subject: Pattern Matching for switch (Second Preview) In-Reply-To: References: <5321c128-ba4d-e405-255a-72025a002e0d@oracle.com> <1bed58ce-015c-289b-5696-6dac3c539f6a@oracle.com> Message-ID: <4fef4bed-da6c-f6c0-46df-bbe551408bae@oracle.com> [ moving to a-s-e ] I get the concern that a type pattern is no longer "just a variable declaration"; that was a nice part of the "patterns aren't really so hard to understand" story.? But I think the usability is likely to be not very good.? Take this example: ??? sealed interface Node { } ??? record AddNode(Node left, Node right) extends Node { } ??? ... ??? Node ni = ... ??? switch (ni) { ??????? case AddNode(Node left, Node right) -> ... There's no instantiation of Node possible here *other than* Node.? Which means we are forcing users to either redundantly type out the instantiation (which can get big), or use casts inside the body when they pull things out of left and right.? (And patterns were supposed to make casts go away.) There's almost no case where someone wants a raw type here. We faced this in method references; when referring to Foo::m, we use the target type to infer the right parameterization of m. Raw types are a migration compatibility thing, but there was no migration compatibility concern with method references, nor is there with patterns, since these are new linguistic forms. What's different here is that we have more type information -- the type of the switch target.? So we can refine the type pattern with additional information from the target.? And I can't conceive of a case where the user wouldn't thank us for this. On 9/30/2021 8:25 AM, Gavin Bierman wrote: > >> 2.? Inference for type patterns.? This one may be a little >> controversial, because we already let this ship sail with type >> patterns in instanceof, but I'm pretty convinced that what we're >> doing right now is wrong. Currently, if we are switching on a >> List, we disallow a type pattern for ArrayList, because >> this would require an unchecked conversion.? This is right. But if we >> have a `case ArrayList a`, the type of `a` is not ArrayList, >> but raw ArrayList.? This is almost always not what the user wants; >> there's no migration compatibility here where the switch target was >> generified but the case labels are not.? Like we do with method >> references, we should infer a reasonable parameterization of >> ArrayList from the match target when a "naked" type shows up in a >> type pattern.? (If the user really wants a raw ArrayList, they can >> switch on a raw List.) >> >> Fixing this for switch is no problem, as it is in preview, but fixing >> this in instanceof requires more care, since there may be production >> code out there.? However, we've generally held that it is OK to infer >> _more_ specific types than have previously been inferred; I doubt >> much code would be impacted -- more likely, some silly casts would go >> away, since users no longer have to cast to ArrayList. > > I?m still unsure about this. Type patterns are treated like variable > declarations - indeed we went to *a lot* of effort to harmonise all > treatments in the JLS regarding variable declarations. What we got to > was very pleasing - even if I say so myself - pattern variable > declarations are just variable declarations with a special treatment > for scope. This proposal will break that because now when a user > declares a pattern variable of type ArrayList they get something else. > Would we not prefer some sort of indication from the user that they > want inference here? What if they do want the raw type? From forax at univ-mlv.fr Thu Sep 30 23:02:13 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 1 Oct 2021 01:02:13 +0200 (CEST) Subject: Pattern Matching for switch (Second Preview) In-Reply-To: <4fef4bed-da6c-f6c0-46df-bbe551408bae@oracle.com> References: <5321c128-ba4d-e405-255a-72025a002e0d@oracle.com> <1bed58ce-015c-289b-5696-6dac3c539f6a@oracle.com> <4fef4bed-da6c-f6c0-46df-bbe551408bae@oracle.com> Message-ID: <2049497550.2253430.1633042933603.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "Gavin Bierman" > Cc: "amber-spec-experts" > Sent: Vendredi 1 Octobre 2021 00:25:26 > Subject: Re: Pattern Matching for switch (Second Preview) > [ moving to a-s-e ] > I get the concern that a type pattern is no longer "just a variable > declaration"; that was a nice part of the "patterns aren't really so hard to > understand" story. But I think the usability is likely to be not very good. > Take this example: > sealed interface Node { } > record AddNode(Node left, Node right) extends Node { } > ... > Node ni = ... > switch (ni) { > case AddNode(Node left, Node right) -> ... > There's no instantiation of Node possible here *other than* Node. Which > means we are forcing users to either redundantly type out the instantiation > (which can get big), or use casts inside the body when they pull things out of > left and right. (And patterns were supposed to make casts go away.) There's > almost no case where someone wants a raw type here. > We faced this in method references; when referring to Foo::m, we use the target > type to infer the right parameterization of m. Raw types are a migration > compatibility thing, but there was no migration compatibility concern with > method references, nor is there with patterns, since these are new linguistic > forms. > What's different here is that we have more type information -- the type of the > switch target. So we can refine the type pattern with additional information > from the target. And I can't conceive of a case where the user wouldn't thank > us for this. I fully agree with Brian. The pattern matching syntax can be visually quite heavy, if we can make the pattern part simpler/lighter visually, it's a win. If a new syntax does not introduce a new way to declare raw types (which does not play well with inference), it's a win. R?mi > On 9/30/2021 8:25 AM, Gavin Bierman wrote: >>> 2. Inference for type patterns. This one may be a little controversial, because >>> we already let this ship sail with type patterns in instanceof, but I'm pretty >>> convinced that what we're doing right now is wrong. Currently, if we are >>> switching on a List, we disallow a type pattern for ArrayList, >>> because this would require an unchecked conversion. This is right. But if we >>> have a `case ArrayList a`, the type of `a` is not ArrayList, but raw >>> ArrayList. This is almost always not what the user wants; there's no migration >>> compatibility here where the switch target was generified but the case labels >>> are not. Like we do with method references, we should infer a reasonable >>> parameterization of ArrayList from the match target when a "naked" type shows >>> up in a type pattern. (If the user really wants a raw ArrayList, they can >>> switch on a raw List.) >>> Fixing this for switch is no problem, as it is in preview, but fixing this in >>> instanceof requires more care, since there may be production code out there. >>> However, we've generally held that it is OK to infer _more_ specific types than >>> have previously been inferred; I doubt much code would be impacted -- more >>> likely, some silly casts would go away, since users no longer have to cast to >>> ArrayList. >> I?m still unsure about this. Type patterns are treated like variable >> declarations - indeed we went to *a lot* of effort to harmonise all treatments >> in the JLS regarding variable declarations. What we got to was very pleasing - >> even if I say so myself - pattern variable declarations are just variable >> declarations with a special treatment for scope. This proposal will break that >> because now when a user declares a pattern variable of type ArrayList they get >> something else. Would we not prefer some sort of indication from the user that >> they want inference here? What if they do want the raw type?