From brian.goetz at oracle.com Mon Mar 1 22:04:56 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 1 Mar 2021 17:04:56 -0500 Subject: Primitive type patterns and conversions Message-ID: Right now, we've spent almost all our time on patterns whose targets are reference types (type patterns, record patterns, array patterns, deconstruction patterns).? It's getting to be time to nail down (a) the semantics of primitive type patterns, and (b) the conversions-and-contexts (JLS 5) rules.? And, because we're on the cusp of the transition to Valhalla, we must be mindful of both both the current set of primitive conversions, and the more general object model as it will apply to primitive classes. If we focus on type patterns alone, let's bear in mind that primitive type patterns are not nearly as powerful as other type patterns, because (under the current rules) primitives are "islands" in the type system -- no supertypes, no subtypes.? In other words, they are *always total* on the types they would be strictly applicable to, which means any conditionality would come from conversions like boxing, unboxing, and widening.? But I'm not sure pattern matching has quite as much to offer these more ad-hoc conversions. We have special rules for integer literals; the literal `0` has a standalone type of `int`, but in most contexts, can be narrowed to `byte`, `short`, or `char` if it fits into the range.? When we were considering constant patterns, we considered whether those rules were helpful for applying in reverse to constant patterns, and concluded that it added a lot of complexity for little benefit.? Now that we've decided against constant patterns for the time being, it may be moot anyway, but let me draw the example as I think it might be helpful. Consider the following switch: ??? int anInt = 300; ??? switch (anInt) { ??????? case byte b:? A ??????? case short s: B ??????? case int i: C ??? } What do we expect to happen?? One interpretation is that `byte b` is a pattern that is applicable to all integral types, and only matches the range of byte values.? (In this interpretation, the second case would match.)? The other is that this is a type error; the patterns `byte b` and `short s` are not applicable to `int`, so the compiler complains.? (In fact, in this interpretation, these patterns are always total, and their main use is in nested patterns.) If your initial reaction is that the first interpretation seems pretty good, beware that the sirens are probably singing to you.? Yes, having the ability to say "does this int fit in a byte" is a reasonable test to want to be able to express.? But cramming this into the semantics of the type pattern `byte b` is an exercise in complexity, since now we have to have special rules for each (from, to) pair of primitives we want to support. Another flavor of this problem is: ??? Object o = new Short(3); ??? switch (o) { ??????? case byte b:? A ??????? case short s: B ??? } 3 can be crammed into a `byte`, and therefore could theoretically match the first case, but is this really the kind of complexity we want to layer atop the definition of primitive type patterns? I think there's a better answer: lean on explicit patterns for conversions.? The conversions from byte <--> int form an embedding projection pair, which means that they are suited for a total factory + partial pattern pair: ??? class int { ??????? static int fromByte(byte b) { return b; } ??????? pattern(byte b) fromByte() { ... succeed if target in range ... } ??? } Then we can replace the first switch with: ??? switch (anInt) { ??????? case fromByte(var b): A??? // static or instance patterns on `int` ??????? case fromShort(var s): B ??? } which is (a) explicit and (b) uses straight library code rather than complex language magic, and (c) scales to non-built-in primitive classes.? (Readers may first think that the name `fromXxx` is backwards, rather than `toXxx`, but what we're asking is: "could this int have come from a byte-to-int conversion".) So, strawman: ??? A primitive type pattern `P p` is applicable _only_ to type `P` (and therefore is always ??? total).? Accordingly, their primary utility is as a nested pattern. Now, let's ask the same questions about boxing and unboxing.? (Boxing is always total; unboxing might NPE.) Here, I think introducing boxing/unboxing conversions into pattern matching per se is even less useful.? If a pattern binds an int, but we wanted an Integer (or vice versa), then we are free (by virtual of boxing/unboxing in assignment and related contexts) to just use the binding.? For example: ??? void m(Integer i) { ... } ??? ... ??? plus some pattern Foo(int x) ??? ... ??? switch (x) { ??????? case Foo(int x): m(x); ??? } We don't care that we got an int out; when we need an Integer, the right thing happens.? In the other direction, we have to worry about NPEs, but we can fix that with pattern tools we have: ??? switch (x) { ??????? case Bar(Integer x & true(x != null)): ... safe to unbox x ... So I think our strawman holds up: primitive type patterns are total on their type, with no added boxing/narrowing/widening weirdness.? We can characterize this as a new context in Ch5 ("conditional pattern match context"), that permits only identity and reference widening conversions.? And when we get to Valhalla, the same is true for type patterns on primitive classes. ** BONUS ROUND ** Now, let's talk about pattern assignment statements, such as: ??? Point(var x, var y) = aPoint The working theory is that the pattern on the LHS must be total on the type of the expression on the RHS, with some remainder allowed, and will throw on any remainder (e.g., can throw NPE on null.)? If we want to align this with the semantics of local variable declaration + initializer, we probably *do* want the full set of assignment-context conversions, which I think is fine in this context (so, a second new context: unconditional pattern assignment, which allows all the same conversions as are allowed in assignment context.) If the set of conversions is the same, then we are well on our way to being able to interpret ??? T t = e as *either* a local variable declaration, *or* a pattern match, without the user being able to tell the difference: ?- The scoping is the same (since the pattern either completes normally or throws); ?- The mutability is the same (we fixed this one just in time); ?- The set of conversions, applicable types, and potential exceptions are the same (exercise left to the reader.) Which means (drum roll) local variable assignment is revealed to have been a degenerate case of pattern match all along.? (And the crowd goes wild.) From amaembo at gmail.com Tue Mar 2 10:47:20 2021 From: amaembo at gmail.com (Tagir Valeev) Date: Tue, 2 Mar 2021 17:47:20 +0700 Subject: Pattern assignment statements (extracted from: Primitive type patterns and conversions) In-Reply-To: References: Message-ID: On Tue, Mar 2, 2021 at 5:05 AM Brian Goetz wrote: > as *either* a local variable declaration, *or* a pattern match, without the user being able to tell the difference: > > - The scoping is the same (since the pattern either completes normally or throws); > - The mutability is the same (we fixed this one just in time); > - The set of conversions, applicable types, and potential exceptions are the same (exercise left to the reader.) A couple of other things to consider: 1. C-style array declaration is not allowed in patterns. 2. For local variable declaration, the initializer can omit `new int[]` part: int data[] = {1, 2, 3} 3. Local variable type may affect the initializer type (e.g. List list = List.of()). What about patterns? Can we say that a `List list` pattern is a total pattern over `List.of()` expression type? With best regards, Tagir Valeev From brian.goetz at oracle.com Tue Mar 2 17:44:27 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 2 Mar 2021 12:44:27 -0500 Subject: [External] : Pattern assignment statements (extracted from: Primitive type patterns and conversions) In-Reply-To: References: Message-ID: <1a16fc77-5527-bdfc-df1c-ab85b55bfd4e@oracle.com> > A couple of other things to consider: > 1. C-style array declaration is not allowed in patterns. Wish we could phase out C-style array decls entirely... > 2. For local variable declaration, the initializer can omit `new int[]` part: > int data[] = {1, 2, 3} Good catch.? For some reason I was thinking this was only for fields, but it works for locals too.? There's a future story about collection literals, but it is unlikely we'll be able to retcon exactly this syntax.? (#include "bikeshed-deterrent.h") We may be able to address this by including a conversion from an array initializer to the target type of a pattern in an unconditional pattern assignment context. > 3. Local variable type may affect the initializer type (e.g. > List list = List.of()). What about patterns? Can we say that a > `List list` pattern is a total pattern over `List.of()` > expression type? Yes, patterns have a notion of a target type, which is the type they have declared as their matchee.? For a type pattern `T t`, that target type is T.? We already evaluate whether the pattern is applicable to the matchee using this type. From forax at univ-mlv.fr Wed Mar 3 10:55:13 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 3 Mar 2021 11:55:13 +0100 (CET) Subject: Two new draft pattern matching JEPs In-Reply-To: References: Message-ID: <1783673547.1765509.1614768913698.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Gavin Bierman" > ?: "amber-spec-experts" > Envoy?: Jeudi 18 F?vrier 2021 13:33:20 > Objet: Two new draft pattern matching JEPs > Dear all, > > The next steps in adding pattern matching to Java are coming! I have drafted two > new JEPs: I'm slowly trying to wrap my head around these drafts > > - Nested Record and Array Patterns: > https://bugs.openjdk.java.net/browse/JDK-8260244 For me the section Pattern Matching and record classes is missing a discussion about the fact that even if the pattern uses a binding, the value of that binding is not accessed (so the corresponding record accessor is not called) until the binding is used at least once. In static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) { if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c), var lr)) { System.out.println("Top-left Corner: " + x); } } The bindings 'y', 'c' or 'lr' are not used so the corresponding record accessors are not called, 'x' is used so the accessors Rectangle.l(), ColoredPoint.p() and Point.x() are called. I also found the code of the translation of "printXCoordOfUpperLeftPointBeforePatterns" confusing, because the local variable 'c' in this example is not related to the binding 'c' in the pattern matching example. static void printXCoordOfUpperLeftPointBeforePatterns(Rectangle r) { if (r == null) { return; } ColoredPoint c = r.l(); // here 'c' should be 'l' not 'c' if (c == null) { return; } Point p = c.p(); if (p == null) { return; } int x = p.x(); System.out.println("Top-left Corner: " + x); } I also think that it will help the reader if record component names in the example are not identifiers with only one or two letters like in record ColoredPoint(Point p, Color c) {} record Rectangle(ColoredPoint ul, ColoredPoint lr) {} While i think that like lambda, using identifiers with not a lot of letters is fine in a pettern, having classical identifiers for record component names will help to make the distinction between the component names and the binding names. > > Comments welcome as always! > > Thanks, > Gavin R?mi From forax at univ-mlv.fr Wed Mar 3 10:58:12 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 3 Mar 2021 11:58:12 +0100 (CET) Subject: Deserialization of a linked list of records In-Reply-To: <4CA862BE-E483-40A5-9D40-D572744FCFB1@oracle.com> References: <132858763.968856.1614377209705.JavaMail.zimbra@u-pem.fr> <4CA862BE-E483-40A5-9D40-D572744FCFB1@oracle.com> Message-ID: <826105913.1771055.1614769092902.JavaMail.zimbra@u-pem.fr> > De: "Chris Hegarty" > ?: "Remi Forax" > Cc: "amber-spec-experts" , "amber-dev" > > Envoy?: Dimanche 28 F?vrier 2021 17:42:41 > Objet: Re: Deserialization of a linked list of records > [resending; in an effort to produce a less garbled version] > Remi, >>> On 26 Feb 2021, at 22:06, Remi Forax < [ mailto:forax at univ-mlv.fr | >> > forax at univ-mlv.fr ] > wrote: >>>[sent to both email list given it's not clear if it's an implementation issue or >> >a spec issue] >>>There is a nice video of how the serialization of records works recently on >> >inside.java >>> [ https://inside.java/2021/02/23/records-met-serialization/ | >> > https://inside.java/2021/02/23/records-met-serialization/ ] > Yes, this is a nice explanation of how record serialization works. >>> In the video, Julia explains that the de-serialization works from bottom-up, so >> > what if the record instances are a linked list ? >> > answer: a stack overflow. > This reproducer fails on my machine with a StackOveflowException when > serializing ( not de-serializing ). > It fails with: > Exception in thread "main" java.lang.StackOverflowError > at > java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1142) > at > java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1577) > at > java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1534) > at > java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1443) > at > java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1186) > at > java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1577) > at > java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1534) > at > java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1443) > at > java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1186) > at > java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1577) > at > java.base/java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1534) > at > java.base/java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1443) > at > java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1186) > at > java.base/java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1577) > ... > The same is true if Link is a normal class ( not a record class ). oops, yes, you are right ! > Records and normal classes share the same code path in OOS when serializing. exact, i now remember that i have reviewed the code that introduces the serialization of records, sorry for the noise. > -Chris. R?mi From forax at univ-mlv.fr Wed Mar 3 11:18:03 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 3 Mar 2021 12:18:03 +0100 (CET) Subject: [External] : Pattern assignment statements (extracted from: Primitive type patterns and conversions) In-Reply-To: <1a16fc77-5527-bdfc-df1c-ab85b55bfd4e@oracle.com> References: <1a16fc77-5527-bdfc-df1c-ab85b55bfd4e@oracle.com> Message-ID: <1595244719.1784338.1614770283406.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Tagir Valeev" > Cc: "amber-spec-experts" > Envoy?: Mardi 2 Mars 2021 18:44:27 > Objet: Re: [External] : Pattern assignment statements (extracted from: Primitive type patterns and conversions) >> A couple of other things to consider: >> 1. C-style array declaration is not allowed in patterns. > > Wish we could phase out C-style array decls entirely... > >> 2. For local variable declaration, the initializer can omit `new int[]` part: >> int data[] = {1, 2, 3} > > Good catch.? For some reason I was thinking this was only for fields, > but it works for locals too.? There's a future story about collection > literals, but it is unlikely we'll be able to retcon exactly this > syntax.? (#include "bikeshed-deterrent.h") the whole story about initializing a local variable with an array is weird, int data[] = {1, 2, 3}; compiles but int data[]; data = {1, 2, 3}; does not. > > We may be able to address this by including a conversion from an array > initializer to the target type of a pattern in an unconditional pattern > assignment context. while if think we have specify inference that uses the target type in order to avoid to avoid type patterns with a lot of redundant type information, i'm not sure emulating that particular quirk of Java is a good idea. > >> 3. Local variable type may affect the initializer type (e.g. >> List list = List.of()). What about patterns? Can we say that a >> `List list` pattern is a total pattern over `List.of()` >> expression type? > > Yes, patterns have a notion of a target type, which is the type they > have declared as their matchee.? For a type pattern `T t`, that target > type is T.? We already evaluate whether the pattern is applicable to the > matchee using this type. yes, but we have to be careful here, as i said earlier, the inference you want when you do an instanceof/cast and the inference we already have can be at odd. By example, with Object o; if (o instanceof List<> list) { .... } inferring List has the current inference algorithm does will lead to a compile error. Does it means we have two kind of inference algorithms ? In that case, how it works when they interact with each other (when i have a switch inside a lambda by example) ? And in that case, what is the inference algorithm used in pattern assignment statements ? At that point, my head explode :) R?mi From forax at univ-mlv.fr Wed Mar 3 12:50:17 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 3 Mar 2021 13:50:17 +0100 (CET) Subject: Two new draft pattern matching JEPs In-Reply-To: References: Message-ID: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Gavin Bierman" > ?: "amber-spec-experts" > Envoy?: Jeudi 18 F?vrier 2021 13:33:20 > Objet: Two new draft pattern matching JEPs > Dear all, > [...] > > - Pattern Matching for switch: https://bugs.openjdk.java.net/browse/JDK-8213076 > > We split them up to try to keep the complexity down, but we might decide to > merge them into a single JEP. Let me know what you think. I think that we have got a little over our head with the idea of replacing the switch guard by the guard pattern + conditional-and pattern. The draft is poor in explanations on why we should do that apart because it's more powerful, which is true but that not how to evaluate a feature. Here, it doesn't seem we are trying to fix a broken feature or adapt an existing feature to Java. It's just more powerful, but with a lot of drawbacks, see below. My main concern is when mixing the deconstructing pattern with the guard + and pattern, those twos (two and a half) doesn't mix well. For a starter, at high level, the idea is to mix patterns and expressions (guards are boolean expressions), but at the same time, we have discussed several times to not allow constants inside patterns to make a clear distinction between patterns and expressions. We have a inconsistency here. The traditional approach for guards cleanly separate the pattern part from the expression part case Rectangle(Point x, Point y) if x > 0 && y > 0 which makes far more sense IMO. The current proposal allows case Rectangle(Point x & true(x > 0), Point y & true(y > 0)) which is IMO far least readable because the clean separation between the patterns and the expressions is missing. There is also a mismatch in term of evaluation, an expression is evaluated from left to right, for a pattern, you have bindings and bindings are all populated at the same time by a deconstructor, this may cause issue, by example, this is legal in term of execution case Rectangle(Point x & true(x > 0 && y > 0), Point y) because at the point where the pattern true(...) is evaluated, the Rectangle has already been destructured, obviously, we can ban this kind of patterns to try to conserve the left to right evaluation but the it will still leak in a debugger, you have access to the value of 'y' before the expression inside true() is called. In term of syntax, currently the parenthesis '(' and ')' are used to define/destructure the inside, either by a deconstructor or by a named pattern, but in both cases the idea is that it's describe the inside. Here, true() and false() doesn't follow that idea, there are escape mode to switch from the pattern world into the expression world. At least we can use different characters for that. Also in term of syntax again, introducing '&' in between patterns overloads the operator '&' with one another meaning, my students already have troubles to make the distinction between & and && in expressions. As i already said earlier, and this is also said in the Python design document, we don't really need an explicit 'and' operator in between patterns because there is already an implicit 'and' between the sub-patterns of a deconstructing pattern. To finish, there is also an issue with the lack of familiarity, when we have designed lambdas, we have take a great care to have a syntax similar to the C#, JS, Scala syntax, the concept of guards is well known, to introduce a competing feature in term of syntax and semantics, the bar has to be set very high because we are forcing people to learn a Java specific syntax, not seen in any other mainstream languages*. For me, the cons far outweigh the pro(s) here, but perhaps i've missed something ? > > Draft language specs are under way - I will announce those as soon as they are > ready. > > Comments welcome as always! regards, > > Thanks, > Gavin R?mi * Java do not invent things, it merely stole ideas from the other and make them its own in a coherent way From brian.goetz at oracle.com Wed Mar 3 16:05:03 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 3 Mar 2021 11:05:03 -0500 Subject: Two new draft pattern matching JEPs In-Reply-To: <1783673547.1765509.1614768913698.JavaMail.zimbra@u-pem.fr> References: <1783673547.1765509.1614768913698.JavaMail.zimbra@u-pem.fr> Message-ID: > For me the section Pattern Matching and record classes is missing a discussion about the fact that even if the pattern uses a binding, > the value of that binding is not accessed (so the corresponding record accessor is not called) until the binding is used at least once. As attractive as that seems, I don't think we want to go there. There are two big problems here. 1.? Specifying in terms of accessors constrains our ability to evolve the language -- in a direction we know we want to go, and soon.? Just as records get the obvious constructor if you don't give them one, they also get the obvious _deconstructor_ if you don't give them one.? But surely, we might want to allow people to explicitly specify the deconstructor, just as they can explicitly specify the constructor.? (The obvious implementation delegates to the accessors.)?? More importantly, we want a "record pattern" to appeal to a deconstructor, so it is useful for all classes, not just records, and we want the semantics of such patterns to be consistent across records and classes, to permit compatible refactoring.? So a record pattern really has to be a degenerate case of a deconstructor pattern.? Giving the accessors more billing than they deserve in this story just distracts from the real star, which is deconstructors. 2.? Specifying that we do not call the accessor unless / until the binding is used effectively creates an expectation of laziness, which is cool but which will be extremely difficult to maintain when we get to more sophisticated patterns.? And if record patterns are lazy but everything else is not, that's just confusing.? (And, worse, we are spending our laziness budget where it matters least -- record accessors are trivially inlined away 99% of the time, so the benefit of laziness for records is almost zero.)? Instead, we should specify that there is no guarantee about the timing (or number of invocations) of the accessor, and let the compiler generate the best code it can. (I spent a lot of time trying to figure out how we can preserve the option for laziness in complex patterns, and it generally falls apart about when it would start to be valuable.) From brian.goetz at oracle.com Wed Mar 3 16:09:08 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 3 Mar 2021 11:09:08 -0500 Subject: [External] : Pattern assignment statements (extracted from: Primitive type patterns and conversions) In-Reply-To: <1595244719.1784338.1614770283406.JavaMail.zimbra@u-pem.fr> References: <1a16fc77-5527-bdfc-df1c-ab85b55bfd4e@oracle.com> <1595244719.1784338.1614770283406.JavaMail.zimbra@u-pem.fr> Message-ID: >>> 2. For local variable declaration, the initializer can omit `new int[]` part: >>> int data[] = {1, 2, 3} >> Good catch.? For some reason I was thinking this was only for fields, >> but it works for locals too.? There's a future story about collection >> literals, but it is unlikely we'll be able to retcon exactly this >> syntax.? (#include "bikeshed-deterrent.h") > the whole story about initializing a local variable with an array is weird, > int data[] = {1, 2, 3}; > compiles but > int data[]; > data = {1, 2, 3}; > does not. True, and I hate it.? This is related to the C-style array decl; for the first year it was really important that Java seem friendly to C developers, and this was one of the compromises.? For the rest of time, it is just an annoying irregularity. > >> We may be able to address this by including a conversion from an array >> initializer to the target type of a pattern in an unconditional pattern >> assignment context. > while if think we have specify inference that uses the target type in order to avoid to avoid type patterns with a lot of redundant type information, > i'm not sure emulating that particular quirk of Java is a good idea. Where I'm going here is that I don't want to introduce a "pattern assignment" statement that is new and different from local variable declaration with initializer; I want to make the semantics align 100%, so that pattern assignment is a pure generalization of local variable declaration with initializer.? We're 96% of the way there. From brian.goetz at oracle.com Wed Mar 3 16:53:29 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 3 Mar 2021 11:53:29 -0500 Subject: Two new draft pattern matching JEPs In-Reply-To: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> Message-ID: <871dca64-dd15-b67c-4cde-c837a0371e5c@oracle.com> > For a starter, at high level, the idea is to mix patterns and expressions (guards are boolean expressions), but at the same time, we have discussed several times to not allow constants inside patterns to make a clear distinction between patterns and expressions. We have a inconsistency here. No, this is not *inconsistent* at all.? (It may be confusing, though.)? We've already discussed how some patterns (e.g., regex) will take input arguments, which are expressions.? We haven't quite nailed down our syntactic conventions for separating input expressions from output bindings, but the notion of a pattern that accepts expressions as input is most decidedly not outside the model. Your real argument here seems to be that this is a design based on where the language is going, and the pieces that we are exposing now don't stand on their own as well as we might like.? (We've seen this before; users can often react very badly to design decisions whose justification is based on where we're going, but haven't gotten.) > The traditional approach for guards cleanly separate the pattern part from the expression part > case Rectangle(Point x, Point y) if x > 0 && y > 0 > which makes far more sense IMO. Yes, we were stuck there for a while as being the "obvious" choice (modulo choice of keyword); to treat this problem as a switch problem, and nail new bits of syntax onto switch. To restate the obvious, this is a considerably weaker construct.? It is (a) switch-specific, and (b) means we cannot intersperse guards with more patterns, we have to do all the patterns and then all the guards (i.e., it doesn't compose.)? In the case of simple switches over records, this extra power is not obviously needed, but its lack will bite us as patterns get more powerful. I think the argument you want to be making here is "OK, so the compositional approach is obviously better and more powerful, but it will put the users off, especially in the simple cases, and most of the cases they will run into for the next few years are simple". This leads you to "so we should do something that is objectively worse, but less challenging to the users."? (Despite this "loaded" way of writing it, this really is the argument you're making.? And it is a valid one -- but please, let's not try to convince ourselves that it is remotely better in any fundamental way, other than "doesn't make people think as hard." Essentially, I think what you're saying is that it is _too early_ to introduce & patterns -- because guards are a weak motivation to do so.? I don't disagree with this point, but it puts us in a bind -- either we don't provide a way to write guarded patterns now (which is not a problem for instanceof, but is for switch), or we nail some bit of terrible syntax onto switch that we're stuck with. These are not good choices. I'm open to "worse is better" arguments, but please, let's not try to convince ourselves that it's not objectively worse. > The current proposal allows > case Rectangle(Point x & true(x > 0), Point y & true(y > 0)) > which is IMO far least readable because the clean separation between the patterns and the expressions is missing. Yes, it does.? But that's not a bug, its a feature.? It allows users to put the guards where they make sense in the specific situation. Just like any feature that gives users flexibility, they have the flexibility to write more or less readable code.? Any compositional mechanism can lead to better or worse compositions.? But that doesn't seem like a very good argument for "force them to always write it the same way, even when that way isn't always what they want." > There is also a mismatch in term of evaluation, an expression is evaluated from left to right, for a pattern, you have bindings and bindings are all populated at the same time by a deconstructor, this may cause issue, by example, this is legal in term of execution > case Rectangle(Point x & true(x > 0 && y > 0), Point y) > because at the point where the pattern true(...) is evaluated, the Rectangle has already been destructured, obviously, we can ban this kind of patterns to try to conserve the left to right evaluation but the it will still leak in a debugger, you have access to the value of 'y' before the expression inside true() is called. I think the existing flow-scoping rules handle this already. Rectangle(Point x & true(x > 0 && y > 0), Point y).? The guard is "downstream" of the `Point x` pattern, but not of the `Point y` pattern or the `Rectangle(P,Q)` pattern.? So I think y is just out of scope at this use by the existing rules.? (But, it's a great test case!) > Also in term of syntax again, introducing '&' in between patterns overloads the operator '&' with one another meaning, my students already have troubles to make the distinction between & and && in expressions. > As i already said earlier, and this is also said in the Python design document, we don't really need an explicit 'and' operator in between patterns because there is already an implicit 'and' between the sub-patterns of a deconstructing pattern. You know the rule: you don't get to pick syntax nits unless you're ready to endorse the rest of the design :) > For me, the cons far outweigh the pro(s) here, but perhaps i've missed something ? You make a lot of arguments here.? I don't buy most of the specifics -- they mostly feel like "XY" arguments -- but I do think that there is an X here, that you're walking in circles around but not naming. So let's try to name that. The closest you get to is this closing argument: > To finish, there is also an issue with the lack of familiarity, when we have designed lambdas, we have take a great care to have a syntax similar to the C#, JS, Scala syntax, the concept of guards is well known, to introduce a competing feature in term of syntax and semantics, the bar has to be set very high because we are forcing people to learn a Java specific syntax, not seen in any other mainstream languages*. which can be interpreted as "what you propose is clearly better than any language with patterns has done, but we may win anyway by doing the silly, ad-hoc, weak thing every other language does, because it will be more familiar to Java developers."?? I think this is a potentially valid argument, but let's be honest about the argument we're making. The other argument you're making is a slightly different one; that the benefits of combining patterns with &, and of having patterns that take expressions as input, is more motivated by _future_ use cases than the ones that this JEP is addressing, and justifying more powerful mechanism "because you're going to need it soon" can be a hard sell. The third argument, which you've not made, but I'll make for you, is one of discoverability.? If you wanted to write ??? case Foo(var x, var y) & true(x > y): it is not obvious that the way to refine a pattern is to & it with a pattern that ignores its target; this requires a more sophisticated notion of what a pattern is than users will have now. In other words, this is "too clever"; it requires users to absorb too many new concepts and combine them in a novel way, when all they want is a simple conditional filter. I think *these* arguments are valid stewardship discussions to be having. As a counterargument, let me point you to this thread from amber-dev 2 years ago (!).? It takes a few messages to get going, but the general idea is "why should we make pattern matching an expression using instanceof, and not just have an `ifmatch` statement?"? My reply, which is a paean to the glory of composition, is here: https://mail.openjdk.java.net/pipermail/amber-dev/2018-December/003842.html This is not to say that this argument is the right answer in all cases, but that it is soooo easy to convince ourselves that an ad-hoc, non-compositional mechanism is "all we'll ever need", and that usually ends in tears. Let's also realize that we're likely to get here eventually anyway; pattern AND is valuable, and eventually you'll be able to write true-like patterns as ordinary declared patterns. So, having laid out the real argument -- that maybe we're getting ahead of ourselves -- are either of the alternatives (no guards, or a guard construct nailed on the side of switch), acceptable?? (Remi, I know your answer, so I'm asking everyone else.) (Or, could it be that the real concern here is just that `true` is ugly?) From john.r.rose at oracle.com Thu Mar 4 07:17:41 2021 From: john.r.rose at oracle.com (John Rose) Date: Wed, 3 Mar 2021 23:17:41 -0800 Subject: [External] : Pattern assignment statements (extracted from: Primitive type patterns and conversions) In-Reply-To: References: <1a16fc77-5527-bdfc-df1c-ab85b55bfd4e@oracle.com> <1595244719.1784338.1614770283406.JavaMail.zimbra@u-pem.fr> Message-ID: <41D9366E-0D9D-4558-B67F-253D87F05CDE@oracle.com> On Mar 3, 2021, at 8:09 AM, Brian Goetz wrote: > >> the whole story about initializing a local variable with an array is weird, >> int data[] = {1, 2, 3}; >> compiles but >> int data[]; >> data = {1, 2, 3}; >> does not. > > True, and I hate it. This is related to the C-style array decl; for the first year it was really important that Java seem friendly to C developers, and this was one of the compromises. For the rest of time, it is just an annoying irregularity. Here are some thoughts on array handling and patterns. Basically, looking at pattern assignment, in the case of arrays, prompts me to think that the more rewarding goal is pattern declarations, not pattern assignments. So I?ll try to compare and contrast those, for arrays and other kinds of patterns. Backing up? First, I agree with Brian that ?int data[] = ?? is horrible; we should have only allowed ?int[] data = ?? and taken the hit early. But the nested-braces notation is good even if you never learned C. As everyone knows, the array declaration syntax uses the declaration type as context for decoding the nested initializer expressions. This allows the programmer to provide the array contents without re-asserting the declared array type when the array instance is created. The ?stuff in braces? array initializer notation is also uniformly available in expressions of the form ?new T[]{ ? }?, with the ?new T[]? header providing exactly the same context to the ?{?}?, as in the array declaration case. (Bit of context: Bill Joy and I added the ?new T[]{ ? }? expression syntax in 1.1, so I?m partial to the ?stuff in braces? notation, and I also think it can be extended and rationalized even further.) If the language required explicitly typed array initializers, we?d have to write stuff like this: int[][] as = new int[][]{ new int[]{ 1, 2 }, new int[]{ 3 } }; We would surely tire of it and add some project-coin-style sugar like today?s syntax: int[][] as = { { 1, 2 }, { 3 } }; The ?var? feature similarly allows users to state a controlling type (or factory) in just one place. var as = new int[][] { { 1, 2 }, { 3 } }; (Here?s a factory example: var as = List.of(List.of(1, 2), List.of(3)); It?s an intriguing problem to cross-generalize ?stuff in braces? syntax to factories and to non-array types. But I digress. We can revisit when/if we do construction expressions.) OK, so as Remi says, we don?t allow: int[][] as; as = { { 1, 2 }, { 3 } }; Nor, in fact, do we allow: var as; as = new int[][] { { 1, 2 }, { 3 } }; I think the two cases are parallel. Because there is contextual type information in the two halves (declaration and assignment) that communicates from one half to the other (from int[][] as to the nested exprs, or from the new int[][] expr to the var as), you can?t break up the declaration without breaking up the communication. So, in the case of int[][] as = {?}, the communication is a sort of very strong, explicit target typing, where the quasi-expression {?} is evaluated in the context of, not only an inferred type (as in a hypothetical ?f({?})?) but an explicitly declared type (?int[][]? before ?as?). Such an explicit header type deserves to be recycled as context to the rest of the declaration, if it is useful there, and indeed it is. (In ?var as = new int[][]{?}? the communication is, well, source typing, or whatever is the opposite of target typing. The source type here is very strong and explicit, but it could also be ?var as = List.of(?)? in which case the source type is implicit, inferred as a result of type checking.) Do arrays scale upward toward user-defined literals or templated construction expressions? Actually, I think we are pretty close. We have spoken at various times about an explicitly typed head followed by an implicitly typed tail for such things, and I think ?new int[][] {?}? is a good ?bellwether? example. And if that?s true, then the duality between ?var as = new int[][]{ ? }? and the stylistically different but semantically equivalent ?int[][] as = { ? }? is also a reusable concept: Allowing the type-rich head to be either a declaration type in a declaration or else an expression-prefix in an expression seems almost a forced move. (Precedents for the type-rich expression head would be a cast or a wrapping function call as with lambdas, or ad hoc syntax as with array or object creation expressions. Newer ideas will surely follow.) When I say this, I?m *not* painting a bikeshed for the templated expressions; the type-rich head doesn?t have to be ?new T? or ?(T)? per se, nor does the ?tail stuff? have to use curly braces. We have spent lots of whiteboard time over the years (almost a decade) talking about specific bikeshed colors for this, or perhaps a whole rainbow of user selectable colors. But there?s no point in reviewing all that now. I do think that our experiences with target typing in pattern matching will help us do something similar with construction expressions (if we go there). The reason is that a construction expression is pretty must ?merely? an arrow-reversed construction expression. (Specifically, the dynamic data flow is reversed. The static flow is probably still left to right.) So all the same information is there, just flowing around in a somewhat different direction. Going back to the thread topic, of pattern assignments, I think you get the clearest notations when you use pattern assignment within the context of a declaration, exactly because you have the most possible ?communication? between the head type of the declaration and whatever type information is in the tail. So I?m not surprised that breaking a matching declaration, into a data-free head and a separate assignment of data to a bare name, doesn?t always work. In fact, I?m surprised it works as well as it does. One more thought: Deconstruction is the same as construction, except for data flow direction. In deconstruction, data flows from a pre-existing target object to its extracted components. In construction, data flows from (injected?) components to a (newly created) target object. It is very desirable, IMO, for the ?two directions? to look and feel somehow similar, in their notations. For arrays, this suggests that while construction looks like this: int[][] output = { inputs? }; var output = new int[][]{ inputs? }; So a deconstruction should look something like this: int[][] {outputs?} = input; //maybe: var {outputs?} = (int[][]) input; I?m saying ?something like? NOT ?exactly like?! By example, the construction ?new Box(x)? is only ?something? like the pattern ?Box(var x)?. And yet their similarity suggests what is true, that they do similar jobs. (One reverses the other.) For a standalone assignment that deconstructs an array, a turned-around array creation expression could make sense, but it?s really ugly: new int[][] {outputs?} = input; //yuck (Compared with deconstructing declarations, the standalone assignment syntax feels like a bridge too far, frankly.) And for factories, maybe: var output = List.of(inputs?); //<= reverses => List of(outputs?) = input; //maybe: var of(outputs?) = (List) input; And for partial extraction methods, maybe: var outputMap = m.with(k, inputVal); //<= reverses => Map<> with(k)(outputVal) = inputMap; //ignore m Their ugly cousins, the standalone assignments seem to want to take this ground: List.of(outputs?) = input; Map<>.with(k)(outputVal) = inputMap; But they shouldn?t, I think. There should a token somewhere that says, ?yes, I do want to assign to stuff, not declare stuff?. Straw man: assign int[][] {outputs?} = input; assign List.of(outputs?) = input; assign Map<>.with(k)(outputVal) = inputMap; The extra syntax is needed if we privilege the convention to declare binding names as needed, rather than to rummage around and assign to them as needed. A better syntax (IMO) would be to mark each pattern binding variable in such a way that if unmarked, it is a newly bound variable, and if marked, it is assigned to a pre-existing variable (which must be in scope). int[][] {assign output, assign output2} = input; assign List.of(assign output, assign output2) = input; assign Map<>.with(k)(assign outputVal) = inputMap; This has two benefits: 1. It?s clear which variables are getting assigned to (and therefore require attention to non-local declarations, from surrounding code). 2. You can mix assignments and bindings in the same pattern. ? John P.S. Another point, only slightly related: If we add syntax support for combined declarations of *frozen* arrays we will run into the limits of the compact array notation, and I think there will be some pressure to make it more flexible. To explain, this works OK: var as = new int[] { 1, 2, 3 }.freeze(); var as = Arrays.freeze(new int[] { 1, 2, 3 }); This doesn?t: var as = new int[][] { { 1, 2 }, { 3 } }.freeze(); because (subtly) the sub-arrays are mutable. Nor does this, although I have seen it used informally: int[] as = { 1, 2, 3 }.freeze(); Even if that is rationalized somehow, this has the same ambiguity problem with mutable subarrays: int[][] as = { { 1, 2 }, { 3 } }.freeze(); Happily, we can start playing with the frozen arrays themselves before we start cooking up sugar for them. After trying out use cases we?ll have a better feel for what sugar we want to add. In the early days it might sometimes look as bad as this: var as = new int[][]{ new int[]{ 1, 2 }.freeze(), new int[]{ 3 }.freeze() }.freeze(); (Or worse, if we are scrupulous about avoiding double copies and use an ArrayBuilder helper. But, one step at a time?) The connection between patterns per se and frozen arrays is quite simple: An array deconstruction pattern works exactly the same on a mutable and on a frozen array. The above is really about the limitations of compact array initializers. Compact notations are inherently difficult to adjust in meaning, at least while preserving compactness. From forax at univ-mlv.fr Thu Mar 4 07:52:17 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 4 Mar 2021 08:52:17 +0100 (CET) Subject: [External] : Pattern assignment statements (extracted from: Primitive type patterns and conversions) In-Reply-To: <41D9366E-0D9D-4558-B67F-253D87F05CDE@oracle.com> References: <1a16fc77-5527-bdfc-df1c-ab85b55bfd4e@oracle.com> <1595244719.1784338.1614770283406.JavaMail.zimbra@u-pem.fr> <41D9366E-0D9D-4558-B67F-253D87F05CDE@oracle.com> Message-ID: <1867674915.140577.1614844337540.JavaMail.zimbra@u-pem.fr> > De: "John Rose" > ?: "Brian Goetz" > Cc: "Remi Forax" , "Tagir Valeev" , > "amber-spec-experts" > Envoy?: Jeudi 4 Mars 2021 08:17:41 > Objet: Re: [External] : Pattern assignment statements (extracted from: Primitive > type patterns and conversions) > On Mar 3, 2021, at 8:09 AM, Brian Goetz < [ mailto:brian.goetz at oracle.com | > brian.goetz at oracle.com ] > wrote: >>> the whole story about initializing a local variable with an array is weird, >>> int data[] = {1, 2, 3}; >>> compiles but >>> int data[]; >>> data = {1, 2, 3}; >>> does not. >> True, and I hate it. This is related to the C-style array decl; for the first >> year it was really important that Java seem friendly to C developers, and this >> was one of the compromises. For the rest of time, it is just an annoying >> irregularity. > Here are some thoughts on array handling > and patterns. > Basically, looking at pattern assignment, in > the case of arrays, prompts me to think that > the more rewarding goal is pattern declarations, > not pattern assignments. So I?ll try to compare > and contrast those, for arrays and other kinds of > patterns. > Backing up? > First, I agree with Brian that ?int data[] = ?? > is horrible; we should have only allowed > ?int[] data = ?? and taken the hit early. There is also its big brother int foo() [] { return null; } which is the epitome of the C-style array declaration allowed in Java. R?mi From john.r.rose at oracle.com Thu Mar 4 08:43:26 2021 From: john.r.rose at oracle.com (John Rose) Date: Thu, 4 Mar 2021 00:43:26 -0800 Subject: Primitive type patterns and conversions In-Reply-To: References: Message-ID: <710130F0-4D89-46BE-B57E-AB2B28853612@oracle.com> On Mar 1, 2021, at 2:04 PM, Brian Goetz wrote: > > Right now, we've spent almost all our time on patterns whose targets are reference types (type patterns, record patterns, array patterns, deconstruction patterns). It's getting to be time to nail down (a) the semantics of primitive type patterns, and (b) the conversions-and-contexts (JLS 5) rules. And, because we're on the cusp of the transition to Valhalla, we must be mindful of both both the current set of primitive conversions, and the more general object model as it will apply to primitive classes. OK, good news and bad news: On the plus side, this generally makes sense and seems useful and powerful. On the minus side, I think you are listening to the wrong siren. When we did JSR 292, we had to decide whether to embrace the primitive conversions (in combinators like asType), or whether to keep our distance. For MH?s the gravitational pull came from the language and reflection. The language freely inserts widening primitive conversions. The reflective APIs also do this. It turns out that aligning MH conversions with those precedents was a little tricky, but once the details were nailed down, the user experience was probably better; you could refactor between MH?s and either hand-written code or reflective code with fewer surprises (in the handling of primitive conversions). > If we focus on type patterns alone, let's bear in mind that primitive type patterns are not nearly as powerful as other type patterns, because (under the current rules) primitives are "islands" in the type system -- no supertypes, no subtypes. The new question is, how many islands? If there are fewer islands, then bytes live in part of Int Island, for example. If there are more, then we have a different ?42? on each island (except Boolean Island). The JLS encourages us to think there are just integral and floating mathematical values, that happen to fit in variously sized boxes. It does this by asserting baldly that byte <: short <: ?. For some meaning of ?<:?, which approximates closely to set inclusion. So I think there are 3-4 islands: certainly boolean, certainly double, probably long (because it has values not in double), maybe char (because it is not fully numeric). Or maybe the set long+double is an island, which happens not to be a denotable type. The JLS doesn?t give us grounds to distinguish two-word from one-word primitives, but if it did int and float would also be islands. As it is, I think they just include seamlessly into long and double. The inclusion mapping is compiled to a i2l or f2d bytecode. It?s not a bug that the same source value can have multiple compiled representations. > In other words, they are *always total* on the types they would be strictly applicable to, which means any conditionality would come from conversions like boxing, unboxing, and widening. But I'm not sure pattern matching has quite as much to offer these more ad-hoc conversions. > > We have special rules for integer literals; the literal `0` has a standalone type of `int`, but in most contexts, can be narrowed to `byte`, `short`, or `char` if it fits into the range. An unmarked literal behaves as if it has a one-point type, which, under set inclusion, is a subtype of whatever standard types happen to contain it. The more intrusive phenomenon is byte <: short <: int <: ?. > When we were considering constant patterns, we considered whether those rules were helpful for applying in reverse to constant patterns, and concluded that it added a lot of complexity for little benefit. Yes, that sounds right. But if we were to add constant patterns, I think we should try hard to embrace the JLS subtyping rules for primitive numbers. At least, I?m glad we did in JSR 292. > Now that we've decided against constant patterns for the time being, it may be moot anyway, but let me draw the example as I think it might be helpful. OK, switching gears to your hypothetical case? > > Consider the following switch: > > int anInt = 300; > > switch (anInt) { > case byte b: A > case short s: B > case int i: C > } > > What do we expect to happen? Drawing on the precedents I mentioned, I think ?anInt? is clearly marked as ?int?. (With ?switch (300)? the target value is unmarked, but that?s a stupid use case.) So we start with an integer value which is in fact typed as ?int?; the treatment of unmarked literals is a red herring. Next, we are asking to perform a dynamic type test, of an int value against a byte case label. If we take the JLS as its word, then byte <: int, and it is legitimate to ask an instance of int whether, in fact, it is also an instance of byte. (This is the ?few islands? interpretation.) Compare switch (anObject) { cast String s: }, which makes an exactly parallel query of a dynamic Object reference to ask if it happens to also be an reference to String. A dynamic type test can also be understood as an attempt to cast a value, to see if something goes wrong. If you have an Object and cast it to (String) and get the object back, you win; it matches a String. Likewise, if you have an int and cast it to (byte) and get the int back (tricksy, but consistent), you win again; it matches a byte. If the conversion either changes the value or throws an exception (which changes the result in a different way), the pattern match should fail. I think this model unifies pattern matching tightly to the rules for conversions in both directions, since if a casts to b, then b (usually) implicitly converts to to a. When a cast would win, the data can flow in all directions without loss. > One interpretation is that `byte b` is a pattern that is applicable to all integral types, and only matches the range of byte values. (In this interpretation, the second case would match.) If we take the JLS at its word then this is a natural and unsurprising interpretation. > The other is that this is a type error; the patterns `byte b` and `short s` are not applicable to `int`, so the compiler complains. (In fact, in this interpretation, these patterns are always total, and their main use is in nested patterns.) At least the compiler will complain, so people who unwittingly rely on the JLS rules won?t get surprised. This is the ?many islands, no sharing? interpretation. > If your initial reaction is that the first interpretation seems pretty good, beware that the sirens are probably singing to you. Yes, having the ability to say "does this int fit in a byte" is a reasonable test to want to be able to express. The siren singing to me is sitting in the middle of the JLS. I think I should listen to her. I don?t care whether the behavior is useful or not; programmers will use it or not as they see fit. > But cramming this into the semantics of the type pattern `byte b` is an exercise in complexity, since now we have to have special rules for each (from, to) pair of primitives we want to support. As it was in JSR 292. > Another flavor of this problem is: > > Object o = new Short(3); > > switch (o) { > case byte b: A > case short s: B > } > > 3 can be crammed into a `byte`, and therefore could theoretically match the first case, but is this really the kind of complexity we want to layer atop the definition of primitive type patterns? Nope. We confronted this in JSR 292 also, and realized that the JLS (bless its heart) didn?t require us to do all that much work. When a reference object is present, then casting to a primitive type P *always* entails a cast to the wrapper type W corresponding to P, and then an unboxing operation, with zero widening or narrowing. (This cast criterion is really just a standard exercise in P/E pair manipulation. The cast is P, the embedding E is an inclusion or approximates one, and the condition is x=E(P(x)), which detects whether x is identified with an element in the range of P.) Thus, applying the cast criterion above to your second example, we find that ?case byte? as applied to Object means ?cast to Byte? followed by ?unbox the byte?. This fails at the first step in the example. The JLS tells you what to do here, I think. At least it told us what to do in JSR 292, in the parallel case. > I think there's a better answer: lean on explicit patterns for conversions. Explicit is good. Implicit is fraught; sometimes good but when it goes bad it?s very mysterious. Since it?s a new part of the language, you can go for explicit, even where corresponding parts of the JLS have already opted for implicit. But if you do that, you may fragment the language needlessly. > The conversions from byte <--> int form an embedding projection pair, In fact, taking the JLS at its word, it is a special P/E pair where the byte -> int arrow is an inclusion (an identity map on one island), not just a mapping between islands. > which means that they are suited for a total factory + partial pattern pair: > > class int { > static int fromByte(byte b) { return b; } > pattern(byte b) fromByte() { ... succeed if target in range ... } > } Yes explicit is good. In fact it may be good enough to rationalize what the JLS says about byte <: short <: int. We can do this by marking those embeddings that are to be treated as identity embeddings: class int { __IdentityEmbedding static int fromByte(byte b) { return b; } __IdentityEmbedding pattern(byte b) fromByte() { ... succeed if target in range ... } } In some other languages, __IdentityEmbedding is spelled ?implicit?. I think ?implicit? conversions can be defined which cannot be understood as identity embeddings, and I suppose that it is such ?rude implicits? which give implicit a bad name. > Then we can replace the first switch with: > > switch (anInt) { > case fromByte(var b): A // static or instance patterns on `int` > case fromShort(var s): B > } Or if those conversions are identity embeddings we can recover the earlier example as a shorthand. > which is (a) explicit and (b) uses straight library code rather than complex language magic, and (c) scales to non-built-in primitive classes. (Readers may first think that the name `fromXxx` is backwards, rather than `toXxx`, but what we're asking is: "could this int have come from a byte-to-int conversion".) (And we are also asking ?if we cast this to a byte, do we get the same value back from the byte??) > > So, strawman: > > A primitive type pattern `P p` is applicable _only_ to type `P` (and therefore is always > total). Accordingly, their primary utility is as a nested pattern. In other words, although reference type patterns respect the reference type hierarchy, we choose to discard the JLS-specified primitive type hierarchy. Instead of identity embeddings (which come from sub/super links), we require ad hoc named embeddings all around. You haven?t persuaded me, yet, that it?s all so very hairy. JSR 292 survived and thrived through a parallel exercise in adhering to the JLS primitive rules. I don?t think you would need a copy of the JLS primitive conversion rules in the pattern matching spec; you just appeal to an ?as if cast? rule or some equivalent. You wouldn?t even need new kinds of conversions, I think. > Now, let's ask the same questions about boxing and unboxing. (Boxing is always total; unboxing might NPE.) > > Here, I think introducing boxing/unboxing conversions into pattern matching per se is even less useful. If a pattern binds an int, but we wanted an Integer (or vice versa), then we are free (by virtual of boxing/unboxing in assignment and related contexts) to just use the binding. For example: > > void m(Integer i) { ... } > ... > plus some pattern Foo(int x) > ... > > switch (x) { > case Foo(int x): m(x); > } > > We don't care that we got an int out; when we need an Integer, the right thing happens. In the other direction, we have to worry about NPEs, but we can fix that with pattern tools we have: > > switch (x) { > case Bar(Integer x & true(x != null)): ... safe to unbox x ... Huh. Or, you could use an as-if-cast rule, and get the null check without further ado. switch (x) { case Bar(int x): ? it was safe to cast (int)x, hence to unbox x ... > So I think our strawman holds up: primitive type patterns are total on their type, with no added boxing/narrowing/widening weirdness. Yeah, none of that JLS weirdness. I hear another siren singing here: ?Ugly mistakes of the past, you shall be fixed?? > We can characterize this as a new context in Ch5 ("conditional pattern match context"), that permits only identity and reference widening conversions. (It?s an odd kind of conversion, one that is never performed, just one that models an restricted set of type relations. I would prefer to lean harder on the ones we have already. Maybe there?s some reason that won?t work. The reasons given above are *not* that reason; the complexity is superficial and dodging it adds other complexity, such as new niche conversion rules and needless asymmetry.) > And when we get to Valhalla, the same is true for type patterns on primitive classes. When we get there, we will still have to decide whether to emulate some or all of the primitive numeric conversions. If we do our homework now, it will be easier then. > ** BONUS ROUND ** > > Now, let's talk about pattern assignment statements, such as: > > Point(var x, var y) = aPoint > > The working theory is that the pattern on the LHS must be total on the type of the expression on the RHS, with some remainder allowed, and will throw on any remainder (e.g., can throw NPE on null. Good. > If we want to align this with the semantics of local variable declaration + initializer, we probably *do* want the full set of assignment-context conversions, which I think is fine in this context (so, a second new context: unconditional pattern assignment, which allows all the same conversions as are allowed in assignment context.) That sounds right. > If the set of conversions is the same, then we are well on our way to being able to interpret > > T t = e > > as *either* a local variable declaration, *or* a pattern match, without the user being able to tell the difference: > > - The scoping is the same (since the pattern either completes normally or throws); > - The mutability is the same (we fixed this one just in time); > - The set of conversions, applicable types, and potential exceptions are the same (exercise left to the reader.) I like this a lot. > Which means (drum roll) local variable assignment is revealed to have been a degenerate case of pattern match all along. (And the crowd goes wild.) Yes, plain assignment also, if we wish. I think the best way to do this may be to use a full declaration with a pattern at its head, but to mark some of the binding variables inside the pattern as ?assign, do not bind fresh?. This would also be useful in ?instanceof? expressions. After finishing our victory dance, we will realize that assignment and declaration still have to be distinguished syntactically. See also my previous message, which started on arrays, and then worked around to list patterns and other extractors, and proposed an ?assign do not bind? marker to request assignment, rather than distinct declaration and assignment syntaxes. HTH! ? John From john.r.rose at oracle.com Thu Mar 4 08:44:31 2021 From: john.r.rose at oracle.com (John Rose) Date: Thu, 4 Mar 2021 00:44:31 -0800 Subject: [External] : Pattern assignment statements (extracted from: Primitive type patterns and conversions) In-Reply-To: <1867674915.140577.1614844337540.JavaMail.zimbra@u-pem.fr> References: <1a16fc77-5527-bdfc-df1c-ab85b55bfd4e@oracle.com> <1595244719.1784338.1614770283406.JavaMail.zimbra@u-pem.fr> <41D9366E-0D9D-4558-B67F-253D87F05CDE@oracle.com> <1867674915.140577.1614844337540.JavaMail.zimbra@u-pem.fr> Message-ID: <34FF550B-07BA-4B45-BCA3-8AB55E634995@oracle.com> On Mar 3, 2021, at 11:52 PM, forax at univ-mlv.fr wrote: >> >> First, I agree with Brian that ?int data[] = ?? >> is horrible; we should have only allowed >> ?int[] data = ?? and taken the hit early. >> > There is also its big brother > int foo() [] { return null; } > which is the epitome of the C-style array declaration allowed in Java. Thanks, Remi; that's the stuff of nightmares and it's bedtime here. From gavin.bierman at oracle.com Thu Mar 4 13:45:09 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Thu, 4 Mar 2021 13:45:09 +0000 Subject: [External] : Re: Two new draft pattern matching JEPs In-Reply-To: <1783673547.1765509.1614768913698.JavaMail.zimbra@u-pem.fr> References: <1783673547.1765509.1614768913698.JavaMail.zimbra@u-pem.fr> Message-ID: <764B419C-C6A5-4546-9313-D4FC755218FD@oracle.com> > On 3 Mar 2021, at 10:55, Remi Forax wrote: > > > I also found the code of the translation of "printXCoordOfUpperLeftPointBeforePatterns" confusing, > because the local variable 'c' in this example is not related to the binding 'c' in the pattern matching example. That?s a good point, thanks. I?ll change that. Gavin From gavin.bierman at oracle.com Thu Mar 4 14:27:41 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Thu, 4 Mar 2021 14:27:41 +0000 Subject: [External] : Re: Two new draft pattern matching JEPs In-Reply-To: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> Message-ID: <9B26624A-C3CA-44BB-AF17-C5DDB5BD5BD1@oracle.com> Hi Remi, I know that Brian has already replied, but just to a couple of your points: > > The traditional approach for guards cleanly separate the pattern part from the expression part > case Rectangle(Point x, Point y) if x > 0 && y > 0 > which makes far more sense IMO. > > The current proposal allows > case Rectangle(Point x & true(x > 0), Point y & true(y > 0)) > which is IMO far least readable because the clean separation between the patterns and the expressions is missing. Well it is, but that?s because you?ve written it in such a way. You could also have written it: case Rectangle(Point x, Point y) & true(x > 0 && y > 0) which isn?t so different from the guards solution. > > There is also a mismatch in term of evaluation, an expression is evaluated from left to right, for a pattern, you have bindings and bindings are all populated at the same time by a deconstructor, this may cause issue, by example, this is legal in term of execution > case Rectangle(Point x & true(x > 0 && y > 0), Point y) > because at the point where the pattern true(...) is evaluated, the Rectangle has already been destructured, obviously, we can ban this kind of patterns to try to conserve the left to right evaluation but the it will still leak in a debugger, you have access to the value of 'y' before the expression inside true() is called. As Brian has pointed out, this pattern is actually ill-formed, as the `y` is not in scope in the guard pattern? But thanks for the feedback. There are two issues here: expressivity and syntax. With increased expressivity, of course you get more ways to write difficult-to-understand code. Sometimes this is important enough for us to reject expressivity, but mostly not. Syntax is, on the other hand, everyone?s favourite subject :-) We could use pretty much any symbol as a pattern operator. Your experience as an educator will certainly be very useful for us to help determine the best choice if we go down this path - do you have any suggestions? Cheers, Gavin PS: I know you left this as a red flag, but just to say that I don?t recognise this mission statement :-) > > * Java do not invent things, it merely stole ideas from the other and make them its own in a coherent way From gavin.bierman at oracle.com Thu Mar 4 15:09:37 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Thu, 4 Mar 2021 15:09:37 +0000 Subject: [External] : Pattern assignment statements (extracted from: Primitive type patterns and conversions) In-Reply-To: <34FF550B-07BA-4B45-BCA3-8AB55E634995@oracle.com> References: <1a16fc77-5527-bdfc-df1c-ab85b55bfd4e@oracle.com> <1595244719.1784338.1614770283406.JavaMail.zimbra@u-pem.fr> <41D9366E-0D9D-4558-B67F-253D87F05CDE@oracle.com> <1867674915.140577.1614844337540.JavaMail.zimbra@u-pem.fr> <34FF550B-07BA-4B45-BCA3-8AB55E634995@oracle.com> Message-ID: > On 4 Mar 2021, at 08:44, John Rose wrote: > > On Mar 3, 2021, at 11:52 PM, forax at univ-mlv.fr wrote: >>> >>> First, I agree with Brian that ?int data[] = ?? >>> is horrible; we should have only allowed >>> ?int[] data = ?? and taken the hit early. >>> >> There is also its big brother >> int foo() [] { return null; } >> which is the epitome of the C-style array declaration allowed in Java. > > Thanks, Remi; that's the stuff of nightmares > and it's bedtime here. :-o I had no idea! I see the JLS says "It is very strongly recommended that this syntax is not used in new code.? Gavin From forax at univ-mlv.fr Thu Mar 4 15:56:35 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 4 Mar 2021 16:56:35 +0100 (CET) Subject: Two new draft pattern matching JEPs In-Reply-To: <871dca64-dd15-b67c-4cde-c837a0371e5c@oracle.com> References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> <871dca64-dd15-b67c-4cde-c837a0371e5c@oracle.com> Message-ID: <1485621441.631660.1614873395892.JavaMail.zimbra@u-pem.fr> I want to separate the discussions about & between patterns and true()/false() aka mix pattern and expressions, because the later is a call for trouble for me, the former is just a question about adding a new pattern or not. I see true() and false() has an heresy because conceptually a bunch of patterns is a kind of declarative API while expressions are not. Allowing expression right inside a pattern means that you can witness the evaluation order of patterns, something we don't want. Here is an example with two patterns that starts with the same prefix private static int COUNTER = 0; private static boolean inc() { COUNTER++; return true; } ... var rectangle = new Rectangle(new Point(1, 2), new Point(2, 1)); switch(rectangle) { case Rectangle(Point x & inc(), null) -> ... case Rectangle(Point x & inc(), Point y) -> ... } what is the value of COUNTER ? The fundamental problem is that you can not skip the evaluation of an expression while you can skip the evaluation of a pattern because either you already have evaluate it or you know that this pattern is not relevant. For a Stream, we can not avoid the lambda of a map or a filter to do a side effect at runtime, here we have the possibility to say that a Pattern is not an expression but a more declarative form. You may object that a deconstructor can do side effect so the point is moot but even with that there is a big difference between allowing someone to create a class with a deconstructor that does a side effect and allowing anyone that write a switch to do any side effects in the middle of the patterns. Now, you are saying that being able to not allow expressions anywhere is a weaker construct, and yes, this is true, but patterns are more than just a nice syntactic constructs, they have the potential to also have a much nicer semantics that will free people to think in term of side effects. (other comments inlined) ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" , "Gavin Bierman" > Cc: "amber-spec-experts" > Envoy?: Mercredi 3 Mars 2021 17:53:29 > Objet: Re: Two new draft pattern matching JEPs >> For a starter, at high level, the idea is to mix patterns and expressions >> (guards are boolean expressions), but at the same time, we have discussed >> several times to not allow constants inside patterns to make a clear >> distinction between patterns and expressions. We have a inconsistency here. > > No, this is not *inconsistent* at all.? (It may be confusing, though.) > We've already discussed how some patterns (e.g., regex) will take input > arguments, which are expressions.? We haven't quite nailed down our > syntactic conventions for separating input expressions from output > bindings, but the notion of a pattern that accepts expressions as input > is most decidedly not outside the model. > > Your real argument here seems to be that this is a design based on where > the language is going, and the pieces that we are exposing now don't > stand on their own as well as we might like.? (We've seen this before; > users can often react very badly to design decisions whose justification > is based on where we're going, but haven't gotten.) There is a misunderstanding here, i'm referring to the fact that case Point(1, 1): is actually rejected because it's too much like new Point(1,1) but at the same time, you want to allow expressions in the middle of patterns. > >> The traditional approach for guards cleanly separate the pattern part from the >> expression part >> case Rectangle(Point x, Point y) if x > 0 && y > 0 >> which makes far more sense IMO. > > Yes, we were stuck there for a while as being the "obvious" choice > (modulo choice of keyword); to treat this problem as a switch problem, > and nail new bits of syntax onto switch. > > To restate the obvious, this is a considerably weaker construct.? It is > (a) switch-specific, and (b) means we cannot intersperse guards with > more patterns, we have to do all the patterns and then all the guards > (i.e., it doesn't compose.)? In the case of simple switches over > records, this extra power is not obviously needed, but its lack will > bite us as patterns get more powerful. For (a), yes it's switch specific and it's great because we don't need it for instanceof, you can already use && inside the if of an instanceof and you don't need it when declaring local variables because the pattern has to be total. So being specific to switch is not really an issue. For (b), you can always shift all the contents of true() and false() to the right into a traditional guard, so we don't need true() and false() [...] > >> There is also a mismatch in term of evaluation, an expression is evaluated from >> left to right, for a pattern, you have bindings and bindings are all populated >> at the same time by a deconstructor, this may cause issue, by example, this is >> legal in term of execution >> case Rectangle(Point x & true(x > 0 && y > 0), Point y) >> because at the point where the pattern true(...) is evaluated, the Rectangle has >> already been destructured, obviously, we can ban this kind of patterns to try >> to conserve the left to right evaluation but the it will still leak in a >> debugger, you have access to the value of 'y' before the expression inside >> true() is called. > > I think the existing flow-scoping rules handle this already. > Rectangle(Point x & true(x > 0 && y > 0), Point y).? The guard is > "downstream" of the `Point x` pattern, but not of the `Point y` pattern > or the `Rectangle(P,Q)` pattern.? So I think y is just out of scope at > this use by the existing rules.? (But, it's a great test case!) My point is that at the time the content of true() is executed, 'x' AND 'y' are already known. So the patterns Rectangle(Point x & true(x > 0 && y > 0), Point y) Rectangle(Point x & true(x > 0), Point y & true(y > 0)) or Rectangle(Point x, Point y) & true(x > 0 && y > 0) are all equivalent in term of runtime execution, so there is little point to have different syntax for the same execution. [...] > > The closest you get to is this closing argument: > >> To finish, there is also an issue with the lack of familiarity, when we have >> designed lambdas, we have take a great care to have a syntax similar to the C#, >> JS, Scala syntax, the concept of guards is well known, to introduce a competing >> feature in term of syntax and semantics, the bar has to be set very high >> because we are forcing people to learn a Java specific syntax, not seen in any >> other mainstream languages*. > > which can be interpreted as "what you propose is clearly better than any > language with patterns has done, but we may win anyway by doing the > silly, ad-hoc, weak thing every other language does, because it will be > more familiar to Java developers."?? I think this is a potentially valid > argument, but let's be honest about the argument we're making. And it's not really better, because most of the combination are just different syntax of the same runtime semantics, which is for me more an issue than anything else. R?mi From forax at univ-mlv.fr Thu Mar 4 16:09:02 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 4 Mar 2021 17:09:02 +0100 (CET) Subject: [External] : Re: Two new draft pattern matching JEPs In-Reply-To: <9B26624A-C3CA-44BB-AF17-C5DDB5BD5BD1@oracle.com> References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> <9B26624A-C3CA-44BB-AF17-C5DDB5BD5BD1@oracle.com> Message-ID: <476579044.639548.1614874142273.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Gavin Bierman" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Jeudi 4 Mars 2021 15:27:41 > Objet: Re: [External] : Re: Two new draft pattern matching JEPs > Hi Remi, > > I know that Brian has already replied, but just to a couple of your points: > >> >> The traditional approach for guards cleanly separate the pattern part from the >> expression part >> case Rectangle(Point x, Point y) if x > 0 && y > 0 >> which makes far more sense IMO. >> >> The current proposal allows >> case Rectangle(Point x & true(x > 0), Point y & true(y > 0)) >> which is IMO far least readable because the clean separation between the >> patterns and the expressions is missing. > > Well it is, but that?s because you?ve written it in such a way. You could also > have written it: > > case Rectangle(Point x, Point y) & true(x > 0 && y > 0) > > which isn?t so different from the guards solution. As i said to Brian, i see this more as an issue, you have several ways to write exactly the same thing. You can see this as "Moar Powaer" or as what is the exact point of introducing a feature that less you express the same thing in twenty different ways. > >> >> There is also a mismatch in term of evaluation, an expression is evaluated from >> left to right, for a pattern, you have bindings and bindings are all populated >> at the same time by a deconstructor, this may cause issue, by example, this is >> legal in term of execution >> case Rectangle(Point x & true(x > 0 && y > 0), Point y) >> because at the point where the pattern true(...) is evaluated, the Rectangle has >> already been destructured, obviously, we can ban this kind of patterns to try >> to conserve the left to right evaluation but the it will still leak in a >> debugger, you have access to the value of 'y' before the expression inside >> true() is called. > > As Brian has pointed out, this pattern is actually ill-formed, as the `y` is not > in scope in the guard pattern? yes, hence my "this is legal in term of execution", at the point where 'x' is know, 'y' is known too, so being able to specify a construct in between whn 'x' is known but 'y' is not yet visible make little sense. > > But thanks for the feedback. There are two issues here: expressivity and syntax. > With increased expressivity, of course you get more ways to write > difficult-to-understand code. Sometimes this is important enough for us to > reject expressivity, but mostly not. Syntax is, on the other hand, everyone?s > favourite subject :-) We could use pretty much any symbol as a pattern > operator. Your experience as an educator will certainly be very useful for us > to help determine the best choice if we go down this path - do you have any > suggestions ? I think it's still a little too soon to talk about syntax. > > Cheers, > Gavin > > PS: I know you left this as a red flag, but just to say that I don?t recognise > this mission statement :-) > >> >> * Java does not invent things, it merely stole ideas from the other and make them > > its own in a coherent way For me, Java is like Picasso, first you stole, then you transcend. regards, R?mi From brian.goetz at oracle.com Thu Mar 4 16:28:05 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 4 Mar 2021 11:28:05 -0500 Subject: [External] : Re: Two new draft pattern matching JEPs In-Reply-To: <1485621441.631660.1614873395892.JavaMail.zimbra@u-pem.fr> References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> <871dca64-dd15-b67c-4cde-c837a0371e5c@oracle.com> <1485621441.631660.1614873395892.JavaMail.zimbra@u-pem.fr> Message-ID: <3855bd20-8406-a7a3-ee5f-d25566a32b8e@oracle.com> > I want to separate the discussions about & between patterns and true()/false() aka mix pattern and expressions, > because the later is a call for trouble for me, the former is just a question about adding a new pattern or not. > > I see true() and false() has an heresy because conceptually a bunch of patterns is a kind of declarative API while expressions are not. If it helps, let me trace the story of where the true/false pattern idea came from.? We have had "method patterns" (need a better name) in the model forever; we have anticipated that some method patterns (e.g., map get, regex match) will distinguish between input and output parameters.? And we have imagined AND and OR combinators for a long time as well. One day, it occurred to me that, with the ultimate pattern model we are envisioning, we don't need a linguistic notion of guards at all!? I could write an ordinary pattern: ??? static pattern guard(boolean b) { ??????? if (!b) __FAIL; ??? } and voila, we can express guards as: ??? case P & guard(e): This was a powerful realization, because it meant that the notion of "guard" was "not a primitive"; it was something we could compose from existing envisioned tools.? Except, we don't have method patterns yet (and are not ready to do them), so ... why not treat the existing reserved identifier `true` as if it were declared like `guard` above? (What's weird about this pattern is that it ignores its target. That's "clever", and clever can be confusing.) I don't say this to justify the syntax; I get that pressing `true` into service may feel a bit of a stretch.? My point here is that the true/false patterns proposed in the JEP are *not special*; the are something we could declare eventually with the model that we expect to get to.? (They're like record patterns, in a sense; eventually all classes will be able to declare deconstruction patterns, but records are special in that they get one even without declaring it.)? So if you find them heretical, then you should also have a problem with expressing map-get or regex match as a pattern, no? And if so, let's put the complaint where it belongs; the use of expressions in true/false is just a symptom of your distress, not the cause.? Let's talk about the cause. > Allowing expression right inside a pattern means that you can witness the evaluation order of patterns, something we don't want. Note that this is unavoidable when we get to declared patterns; if there are side effects, you can witness their order and arity.? But, this is also not your real point.? So, let's get to the real point, so we can discuss that, because whether we leak an unspecified order of evaluation through side-effects is mostly a distraction. > There is a misunderstanding here, i'm referring to the fact that > case Point(1, 1): > is actually rejected because it's too much like new Point(1,1) but at the same time, you want to allow expressions in the middle of patterns. Actually, the opposite! A lot of people (initially, me included) would like to just interpret a boolean expression as a pattern: ??? case Foo(var x, var y) && x > y: ... I found that goal compelling, but as patterns get more complicated, this gets unreadable.? A main benefit of the true() patterns (or, the explicit guard() pattern declared above) is that it "quarantines" the expression in an expression wrapper; there's a clear boundary between "pattern world" and "expression world". In any case, you are distorting the claim of "no expressions in the middle of patterns."? We have always envisioned specific contexts where expressions can be dropped into patterns (map get, regex), but we have worked to ensure there is a *clear syntactic division*, so that it is clear from the syntactic structure whether something is a pattern or an expression. > For (a), yes it's switch specific and it's great because we don't need it for instanceof, you can already use && inside the if of an instanceof and you don't need it when declaring local variables because the pattern has to be total. So being specific to switch is not really an issue. Only true if `instanceof` and `switch` are the only contexts where you can imagine patterns. What about, say, `catch`?? If you nail a bag on the side of switch, you will have to nail the same bag on the side of catch.? Which might be acceptable, and the same bag might fit, but that's yet more ad-hoc language surface. > For (b), you can always shift all the contents of true() and false() to the right into a traditional guard, so we don't need true() and false() In theory true, but only in a world where pattern evaluation is cost-free, exception-free, and side-effect free.? (That's why && is short circuiting -- because these things are not always true.) I'm not deaf to the argument that "nailing a bag on the side will be easier for developers to accept", but please, let's stop pretending it's not a bag.? If you want to advocate for "bag", then please, make that case directly! From brian.goetz at oracle.com Thu Mar 4 17:05:18 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 4 Mar 2021 12:05:18 -0500 Subject: Fwd: Two new draft pattern matching JEPs In-Reply-To: References: Message-ID: <4e038546-59d6-d6d7-e4cc-5c32a990b656@oracle.com> Received on the -comments list. Analysis from the legislative analyst: This comment amounts to "Well, if you could eventually write the true/false patterns as declared patterns which ignore their target, then just do declared patterns now, and just make them declared patterns."? (Which is exactly what kicked off this direction -- that guards could be expressed as declared patterns which ignore their target.) When lumping the features together for a delivery, there's a balance to be struck, of delivering incremental value vs delivering the entire story at once.? The JEPs proposed at this point are pretty close to being a useful increment of value without overly constraining the remainder of the story, but guards are an area where it is tempting to "borrow from the future." Of course if we could do everything at once, we wouldn't be worrying about balancing the short term with the long. But, delaying further pattern matching progress until we have a full story for declared patterns seemed a bit extreme. So it's not that we missed that route -- indeed, that's the route that got us to the current position -- it's just that route was rejected as "would delay delivering real value now." -------- Forwarded Message -------- Subject: Re: Two new draft pattern matching JEPs Date: Thu, 4 Mar 2021 17:34:15 +0100 From: Victor Nazarov To: amber-spec-comments at openjdk.java.net Hello Java experts, I've been following the discussion about new JEPs for pattern matching and I've observed a controversy considering the introduction of Pattern guards. It seems that what Brian Goetz stated as a problem is: > * either we > don't provide a way to write guarded patterns now (which is not a > problem for instanceof, but is for switch), or > * we nail some bit of terrible syntax onto the switch that we're stuck with. But from my understanding this misses another route: > We've already discussed how some patterns (e.g., regex) will take input > arguments, which are expressions. We haven't quite nailed down our > syntactic conventions for separating input expressions from output > bindings, but the notion of a pattern that accepts expressions as input > is most decidedly not outside the model. When patterns with arguments become available users are able to write code like the following (with imaginary syntax). ```` String s = "aabb"; String result = switch (s) { case String.["(a+)(b+)"]matches(var as, var bs) -> bs + as; default -> "no match"; } ```` Having this ability nothing prevents users to define a `guard` pattern in their library and to use it like: ```` case Rectangle(Point x, Point y) & Utils.[x > 0 && y > 0]guard() ```` For me it seems a good solution to introduce a more general mechanism (patterns with input arguments) and use it to define a library `guard` pattern then to nail some additional syntax (true/false overload). So returning to the original problem then I think a possible solution is to introduce some special `guard` library pattern right away. Cons: * Need to decide on special syntax for input arguments right away * Hard to specify that custom patterns with input arguments are not yet available and only special library `guard` patterns can use this feature. Pros: * Less special syntax in the language, because input arguments are going to be introduced anyway * It is probably easier to explain to users the usefulness of `&` because that way users can already see that not only destructuring pattern are going to be available, but more generic and complex patterns with input arguments are going to be available. -- Victor Nazarov From brian.goetz at oracle.com Thu Mar 4 18:19:19 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 4 Mar 2021 13:19:19 -0500 Subject: Fwd: Two new draft pattern matching JEPs In-Reply-To: <4e038546-59d6-d6d7-e4cc-5c32a990b656@oracle.com> References: <4e038546-59d6-d6d7-e4cc-5c32a990b656@oracle.com> Message-ID: <0a7c77fa-a5cb-df2f-543e-c0ae297ac6da@oracle.com> Lots of people (Remi here, VictorN on a-comments, StephenC on a-dev) are complaining about true/false patterns, but my take is that we've not really gotten to the real objections; instead, I'm seeing mostly post-hoc rationalizations that try and capture the objections (understandable), but I don't think we've really hit the mark yet.? As I've said, I believe there might be something "there" there, but the arguments made so far have not yet captured it, so I don't really know how to proceed.? But, clearly this has pushed people's buttons, so let's try to drill in. One possible objection is indirectness / discoverability; that to refine a pattern, you have to find some other pattern that captures the refinement, and combine them with an operator that isn't used for anything else yet.? This contributes to making it feel like an "incantation". Another possible object is more superficial, but may be no less real.? I had a private exchange with AndreyN, which revealed two potentially useful bits of information: ?- It's possible that the bad reaction to true(e) is that, because we're so wired to seeing `true` as a constant, the idea of seeing it as a method/pattern name is foreign; ?- Because true is a reserved identifier, the possibility to later pull back the curtain and say "look, true(e) is not magic, it's just a statically imported declared pattern!" is limited. So by picking true/false now, we miss an opportunity to unify later. So, here's a thought experiment, not so much as a concrete proposal, but as a "how does this change how we think about it" query; imagine we picked another identifier, with the plan of ultimately exposing it to be just an ordinary method pattern later. I'll use the obviously stupid "grobble" in this example, to avoid inclinations to paint the shed.? So you'd write: ??? case Foo(var x, var y) & grobble(x > y): ... and in Java 17, "grobble" would be specified to be an ad-hoc guard pattern, but in Java N > 17, we would be able to pull back the curtain and say "behold, the long-hidden declaration of grobble, which we've conveniently static-imported for you": ??? static pattern(void) grobble(boolean expr) { ??????? if (!expr) ??????????? __FAIL; ??? } This would allow us to borrow from the future, while allowing the temporary hack to be turned into something legitimate later. So, control question: if we had said "grobble" instead of "true", does that change the perception of how ugly, foreign, or roundabout ??? case Foo(var x, var y) & grobble(x > y): ... is? Direct answers only, initially. On 3/4/2021 12:05 PM, Brian Goetz wrote: > Received on the -comments list. > > Analysis from the legislative analyst: > > This comment amounts to "Well, if you could eventually write the > true/false patterns as declared patterns which ignore their target, > then just do declared patterns now, and just make them declared > patterns."? (Which is exactly what kicked off this direction -- that > guards could be expressed as declared patterns which ignore their > target.) > > When lumping the features together for a delivery, there's a balance > to be struck, of delivering incremental value vs delivering the entire > story at once.? The JEPs proposed at this point are pretty close to > being a useful increment of value without overly constraining the > remainder of the story, but guards are an area where it is tempting to > "borrow from the future." Of course if we could do everything at once, > we wouldn't be worrying about balancing the short term with the long. > But, delaying further pattern matching progress until we have a full > story for declared patterns seemed a bit extreme. > > So it's not that we missed that route -- indeed, that's the route that > got us to the current position -- it's just that route was rejected as > "would delay delivering real value now." > > > -------- Forwarded Message -------- > Subject: Re: Two new draft pattern matching JEPs > Date: Thu, 4 Mar 2021 17:34:15 +0100 > From: Victor Nazarov > To: amber-spec-comments at openjdk.java.net > > > > Hello Java experts, > > I've been following the discussion about new JEPs for pattern matching and > I've observed a controversy considering the introduction of Pattern > guards. > > It seems that what Brian Goetz stated as a problem is: > >> * either we >> don't provide a way to write guarded patterns now (which is not a >> problem for instanceof, but is for switch), or >> * we nail some bit of terrible syntax onto the switch that we're stuck > with. > > But from my understanding this misses another route: > >> We've already discussed how some patterns (e.g., regex) will take input >> arguments, which are expressions. We haven't quite nailed down our >> syntactic conventions for separating input expressions from output >> bindings, but the notion of a pattern that accepts expressions as input >> is most decidedly not outside the model. > > When patterns with arguments become available users are able to write code > like the following (with imaginary syntax). > > ```` > String s = "aabb"; > String result = switch (s) { > case String.["(a+)(b+)"]matches(var as, var bs) -> bs + as; > default -> "no match"; > } > ```` > > Having this ability nothing prevents users to define a `guard` pattern in > their library and to use it like: > > ```` > case Rectangle(Point x, Point y) & Utils.[x > 0 && y > 0]guard() > ```` > > For me it seems a good solution to introduce a more general mechanism > (patterns with input arguments) and use it to define a library `guard` > pattern then to nail some additional syntax (true/false overload). > > So returning to the original problem then I think a possible solution > is to > introduce some special `guard` library pattern right away. > > Cons: > * Need to decide on special syntax for input arguments right away > * Hard to specify that custom patterns with input arguments are not yet > available and only special library `guard` patterns can use this feature. > > Pros: > * Less special syntax in the language, because input arguments are going > to be introduced anyway > * It is probably easier to explain to users the usefulness of `&` because > that way users can already see that not only destructuring pattern are > going to be available, but more generic and complex patterns with input > arguments are going to be available. > > -- > Victor Nazarov From amalloy at google.com Thu Mar 4 20:40:32 2021 From: amalloy at google.com (Alan Malloy) Date: Thu, 4 Mar 2021 12:40:32 -0800 Subject: Fwd: Two new draft pattern matching JEPs In-Reply-To: <0a7c77fa-a5cb-df2f-543e-c0ae297ac6da@oracle.com> References: <4e038546-59d6-d6d7-e4cc-5c32a990b656@oracle.com> <0a7c77fa-a5cb-df2f-543e-c0ae297ac6da@oracle.com> Message-ID: I have certainly experienced a double take when seeing true(expr). I can step back and recognize that it's just some arbitrary syntactical choice, and true(x) is evocative of the question "is x true?", but at first glance it is not appealing. Repurposing a language keyword in this way is surprising; it seems silly, but I think part of it is like, "my IDE has always used a different font for true than for method calls, and wouldn't it look weird to have something with that font have parentheses after it?". Obviously IDEs will adapt, and to complain about anticipated IDE font rendering would be even worse than Brian's usual bugbear about premature syntax discussions. I'm just getting at that true currently occupies a very specific place in my mental headspace, and I'm not sure that I'm happy to stretch it if all we get is the evocative "is x true?" reading. I also don't really care for the natural symmetry with false. I'd rather have one way to express "protect this pattern-match clause with a guard" than two - I never was very excited by perl's `unless` keyword, for when you want to write an `if` but backward. If we had true(expr), we'd surely be asked to add false(expr), which sounds like an obvious feature but I don't imagine would lead to more readable code very often. grobble seems fine. When discussion of switch patterns started, I was one of the people clamoring (quietly) for guards, and I imagined either a single operator or a single keyword separating the (entire) pattern from its guard expression (singular). But the idea of seeing guards as just a special kind of pattern, and for patterns to be composable with each other as a more general kind of guard, appeals to me. Being able to match two patterns against the same object (and-patterns) is actually a feature I thought Haskell and Scala already had, via their as-pattern - but it turns out you can only actually name a variable, not an entire pattern, in their as-pattern slot. In Clojure, at least, generally anywhere a variable name could be bound, you can bind a destructuring form instead - and this includes the :as slot. If we're going to have and-patterns (which, again, I think are nice), it seems quite neat to have guards be just a special case of that, and all it "costs" is that instead of a single keyword or operator separating guards we have an operator and then a keyword (later revealed to be an ordinary library pattern) separating the pattern from its guard. The one objection I still have to grobble is one I've raised before: it makes it hard to imagine ever representing disjunctions as guards. I always bring up stuff like switch (people) { case Pair(Person(String name1, int age1), Person(String name2, int age2)) | age1 > age2 -> name1 + " is older" | age2 > age1 -> name2 + " is older" | otherwise -> format("%s and %s are the same age", name1, name2) } Three cases, but you don't have to repeat the entire pattern three times, just guard the parts you care about. This makes it useful for guards to not just be degenerate patterns, but to be their own separate thing that can refine a pattern. With grobble and and-patterns I don't see a nice way of spelling this very useful feature. People often answer: "Just match the Pair once, extracting its variables, and write an if/else chain under it", but that doesn't combine very well with fall-through: if it's possible for all your guard clauses to be false you'd like to fall through to the next pattern (maybe when someone's name is "Brian" I want to fall through to a pattern handling Collection instead of Pair). On Thu, Mar 4, 2021 at 10:19 AM Brian Goetz wrote: > Lots of people (Remi here, VictorN on a-comments, StephenC on a-dev) are > complaining about true/false patterns, but my take is that we've not really > gotten to the real objections; instead, I'm seeing mostly post-hoc > rationalizations that try and capture the objections (understandable), but > I don't think we've really hit the mark yet. As I've said, I believe there > might be something "there" there, but the arguments made so far have not > yet captured it, so I don't really know how to proceed. But, clearly this > has pushed people's buttons, so let's try to drill in. > > > One possible objection is indirectness / discoverability; that to refine a > pattern, you have to find some other pattern that captures the refinement, > and combine them with an operator that isn't used for anything else yet. > This contributes to making it feel like an "incantation". > > Another possible object is more superficial, but may be no less real. I > had a private exchange with AndreyN, which revealed two potentially useful > bits of information: > > - It's possible that the bad reaction to true(e) is that, because we're > so wired to seeing `true` as a constant, the idea of seeing it as a > method/pattern name is foreign; > > - Because true is a reserved identifier, the possibility to later pull > back the curtain and say "look, true(e) is not magic, it's just a > statically imported declared pattern!" is limited. So by picking > true/false now, we miss an opportunity to unify later. > > So, here's a thought experiment, not so much as a concrete proposal, but > as a "how does this change how we think about it" query; imagine we picked > another identifier, with the plan of ultimately exposing it to be just an > ordinary method pattern later. I'll use the obviously stupid "grobble" > in this example, to avoid inclinations to paint the shed. So you'd write: > > case Foo(var x, var y) & grobble(x > y): ... > > and in Java 17, "grobble" would be specified to be an ad-hoc guard > pattern, but in Java N > 17, we would be able to pull back the curtain and > say "behold, the long-hidden declaration of grobble, which we've > conveniently static-imported for you": > > static pattern(void) grobble(boolean expr) { > if (!expr) > __FAIL; > } > > This would allow us to borrow from the future, while allowing the > temporary hack to be turned into something legitimate later. > > So, control question: if we had said "grobble" instead of "true", does > that change the perception of how ugly, foreign, or roundabout > > case Foo(var x, var y) & grobble(x > y): ... > > is? > > Direct answers only, initially. > > On 3/4/2021 12:05 PM, Brian Goetz wrote: > > Received on the -comments list. > > Analysis from the legislative analyst: > > This comment amounts to "Well, if you could eventually write the > true/false patterns as declared patterns which ignore their target, then > just do declared patterns now, and just make them declared patterns." > (Which is exactly what kicked off this direction -- that guards could be > expressed as declared patterns which ignore their target.) > > When lumping the features together for a delivery, there's a balance to be > struck, of delivering incremental value vs delivering the entire story at > once. The JEPs proposed at this point are pretty close to being a useful > increment of value without overly constraining the remainder of the story, > but guards are an area where it is tempting to "borrow from the future." Of > course if we could do everything at once, we wouldn't be worrying about > balancing the short term with the long. But, delaying further pattern > matching progress until we have a full story for declared patterns seemed a > bit extreme. > > So it's not that we missed that route -- indeed, that's the route that got > us to the current position -- it's just that route was rejected as "would > delay delivering real value now." > > > -------- Forwarded Message -------- > Subject: Re: Two new draft pattern matching JEPs > Date: Thu, 4 Mar 2021 17:34:15 +0100 > From: Victor Nazarov > > To: amber-spec-comments at openjdk.java.net > > Hello Java experts, > > I've been following the discussion about new JEPs for pattern matching and > I've observed a controversy considering the introduction of Pattern guards. > > It seems that what Brian Goetz stated as a problem is: > > * either we > don't provide a way to write guarded patterns now (which is not a > problem for instanceof, but is for switch), or > * we nail some bit of terrible syntax onto the switch that we're stuck > > with. > > But from my understanding this misses another route: > > We've already discussed how some patterns (e.g., regex) will take input > arguments, which are expressions. We haven't quite nailed down our > syntactic conventions for separating input expressions from output > bindings, but the notion of a pattern that accepts expressions as input > is most decidedly not outside the model. > > > When patterns with arguments become available users are able to write code > like the following (with imaginary syntax). > > ```` > String s = "aabb"; > String result = switch (s) { > case String.["(a+)(b+)"]matches(var as, var bs) -> bs + as; > default -> "no match"; > } > ```` > > Having this ability nothing prevents users to define a `guard` pattern in > their library and to use it like: > > ```` > case Rectangle(Point x, Point y) & Utils.[x > 0 && y > 0]guard() > ```` > > For me it seems a good solution to introduce a more general mechanism > (patterns with input arguments) and use it to define a library `guard` > pattern then to nail some additional syntax (true/false overload). > > So returning to the original problem then I think a possible solution is to > introduce some special `guard` library pattern right away. > > Cons: > * Need to decide on special syntax for input arguments right away > * Hard to specify that custom patterns with input arguments are not yet > available and only special library `guard` patterns can use this feature. > > Pros: > * Less special syntax in the language, because input arguments are going > to be introduced anyway > * It is probably easier to explain to users the usefulness of `&` because > that way users can already see that not only destructuring pattern are > going to be available, but more generic and complex patterns with input > arguments are going to be available. > > -- > Victor Nazarov > > > From brian.goetz at oracle.com Thu Mar 4 21:09:45 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 4 Mar 2021 16:09:45 -0500 Subject: [External] : Re: Fwd: Two new draft pattern matching JEPs In-Reply-To: References: <4e038546-59d6-d6d7-e4cc-5c32a990b656@oracle.com> <0a7c77fa-a5cb-df2f-543e-c0ae297ac6da@oracle.com> Message-ID: <2453b210-0cf8-5fa8-cf8e-b4bb809ae496@oracle.com> Thanks Alan, this kind of confirms what I was thinking. I agree that while it might seem "silly" that we react to `true(x)` as being contrary to our expectations of truth, it is real, and after asking a few other sensible people who can sit on their bikeshed-impulses, seems fairly common.? So there's something to that. Agree also that `false` adds relatively little, and may even subtract if people mix them.? So its there only "for consistency", and FC-arguments should always be suspect. I'll think more about your wish again for "sub patterns"; I do expect it will be common to have things like: ?? case P & g1: ?? case P & g2: and avoiding repeating P in all its glory seems a worthy goal. Eliminating the repetition is likely to be more readable and less error-prone, and, as a bonus, probably reduces the need for the compiler to try and be clever and transform the above into a form that avoids repeating P, because the user will already have said what they mean.? (I assume that your | trick works at an arbitrary nesting depth, which probably works better with a sig-whitespace language like Haskell.) On the true-vs-grobble front, Victor's note pushed me to think some more about what a unification would look like, and it is not as pretty as first thought.? In the general case, a pattern has two argument lists: a set of input expressions, and a set of output bindings.? For most patterns, the former will be empty, and so we want to be able to elide it.? From a parsing perspective, what goes in the first list is Expression, but what goes in the second list is Pattern.? If we stick to the strawman: ??? case Foo(inputArgs)(bindings): we can easily define this so the first list is optional when empty.? But for grobble, it is the _second_ list that is empty, and we can't define a parser rule that allows either to be elided, unless the productions for expression and pattern are completely disjoint (which they are not yet.)? Of course, we could pick other syntaxes (#include "bikeshed-deterrent.h"), such as `Foo[inArgs](outBindings)`, which avoids this problem, but then a guard would look like `grobble[expr]`, which looks more like an array dereference than a guard. All this is to say that, it may not be an easy lift to ultimately unify `grobble` as an ordinary declared pattern, but that was probably only a nice-to-have.? But what I'm taking from your comment (and others) is that some sort of non-boolean `grobble` would be OK as a special form, and probably less doubletake-inducing than true/false. (BTW, I suspect the most common guard will be `true(x != null)`, for which having a special form `non-null(x)` might be nicer.) On 3/4/2021 3:40 PM, Alan Malloy wrote: > I have certainly experienced a double take when seeing true(expr). I > can step back and recognize that it's just some arbitrary syntactical > choice, and true(x) is evocative of the question "is x true?", but at > first glance it is not appealing. Repurposing a language keyword in > this way is surprising; it seems silly, but I think part of it is > like, "my IDE has always used a different font for true than for > method calls, and wouldn't it look weird to have something with that > font have parentheses after it?". Obviously IDEs will adapt, and to > complain about anticipated IDE font rendering would be even worse than > Brian's usual bugbear about premature syntax discussions. I'm just > getting at that true currently occupies a very specific place in my > mental headspace, and I'm not sure that I'm happy to stretch it if all > we get is the evocative "is x true?" reading. > > I also don't really care for the natural symmetry with false. I'd > rather have one way to express "protect this pattern-match clause with > a guard" than two - I never was very excited by perl's `unless` > keyword, for when you want to write an `if` but backward. If we had > true(expr), we'd surely be asked to add false(expr), which sounds like > an obvious feature but I don't imagine would lead to more readable > code very often. > > grobble seems fine. When discussion of switch patterns started, I was > one of the people clamoring (quietly) for guards, and I imagined > either a single operator or a single keyword separating the (entire) > pattern from its guard expression (singular). But the idea of seeing > guards as just a special kind of pattern, and for patterns to be > composable with each other as a more general kind of guard, appeals to > me. Being able to match two patterns against the same object > (and-patterns) is actually a feature I thought Haskell and Scala > already had, via their as-pattern - but it turns out you can only > actually name a variable, not an entire pattern, in their as-pattern > slot. In Clojure, at least, generally anywhere a variable name could > be bound, you can bind a destructuring form instead - and this > includes the :as slot. If we're going to have and-patterns (which, > again, I think are nice), it seems quite neat to have guards be just a > special case of that, and all it "costs" is that instead of a single > keyword or operator separating guards we have an operator and then a > keyword (later revealed to be an ordinary library pattern) separating > the pattern from its guard. > > The one objection I still have to grobble is one I've raised before: > it makes it hard to imagine ever representing disjunctions as guards. > I always bring up stuff like > > switch (people) { > ? case Pair(Person(String name1, int age1), Person(String name2, int > age2)) > ? ? | age1 > age2 -> name1?+ " is older" > ? ? | age2 > age1 -> name2?+ " is older" > ? ? | otherwise -> format("%s and %s are the same age", name1, name2) > } > > Three cases, but you don't have to repeat the entire pattern three > times, just guard the parts you care about. This makes it useful for > guards to not just be degenerate patterns, but to be their own > separate thing that can refine?a pattern. With grobble and > and-patterns I don't see a nice way of spelling this very useful > feature. People often answer: "Just match the Pair once, extracting > its variables, and write an if/else chain under it", but that doesn't > combine very well with fall-through: if it's possible for all your > guard clauses to be false you'd like to fall through to the next > pattern (maybe when someone's name is "Brian" I want to fall through > to a pattern handling Collection instead of Pair). > > On Thu, Mar 4, 2021 at 10:19 AM Brian Goetz > wrote: > > Lots of people (Remi here, VictorN on a-comments, StephenC on > a-dev) are complaining about true/false patterns, but my take is > that we've not really gotten to the real objections; instead, I'm > seeing mostly post-hoc rationalizations that try and capture the > objections (understandable), but I don't think we've really hit > the mark yet.? As I've said, I believe there might be something > "there" there, but the arguments made so far have not yet captured > it, so I don't really know how to proceed. But, clearly this has > pushed people's buttons, so let's try to drill in. > > > One possible objection is indirectness / discoverability; that to > refine a pattern, you have to find some other pattern that > captures the refinement, and combine them with an operator that > isn't used for anything else yet.? This contributes to making it > feel like an "incantation". > > Another possible object is more superficial, but may be no less > real.? I had a private exchange with AndreyN, which revealed two > potentially useful bits of information: > > ?- It's possible that the bad reaction to true(e) is that, because > we're so wired to seeing `true` as a constant, the idea of seeing > it as a method/pattern name is foreign; > > ?- Because true is a reserved identifier, the possibility to later > pull back the curtain and say "look, true(e) is not magic, it's > just a statically imported declared pattern!" is limited.? So by > picking true/false now, we miss an opportunity to unify later. > > So, here's a thought experiment, not so much as a concrete > proposal, but as a "how does this change how we think about it" > query; imagine we picked another identifier, with the plan of > ultimately exposing it to be just an ordinary method pattern > later. I'll use the obviously stupid "grobble" in this example, to > avoid inclinations to paint the shed.? So you'd write: > > ??? case Foo(var x, var y) & grobble(x > y): ... > > and in Java 17, "grobble" would be specified to be an ad-hoc guard > pattern, but in Java N > 17, we would be able to pull back the > curtain and say "behold, the long-hidden declaration of grobble, > which we've conveniently static-imported for you": > > ??? static pattern(void) grobble(boolean expr) { > ??????? if (!expr) > ??????????? __FAIL; > ??? } > > This would allow us to borrow from the future, while allowing the > temporary hack to be turned into something legitimate later. > > So, control question: if we had said "grobble" instead of "true", > does that change the perception of how ugly, foreign, or roundabout > > ??? case Foo(var x, var y) & grobble(x > y): ... > > is? > > Direct answers only, initially. > > On 3/4/2021 12:05 PM, Brian Goetz wrote: >> Received on the -comments list. >> >> Analysis from the legislative analyst: >> >> This comment amounts to "Well, if you could eventually write the >> true/false patterns as declared patterns which ignore their >> target, then just do declared patterns now, and just make them >> declared patterns." (Which is exactly what kicked off this >> direction -- that guards could be expressed as declared patterns >> which ignore their target.) >> >> When lumping the features together for a delivery, there's a >> balance to be struck, of delivering incremental value vs >> delivering the entire story at once.? The JEPs proposed at this >> point are pretty close to being a useful increment of value >> without overly constraining the remainder of the story, but >> guards are an area where it is tempting to "borrow from the >> future." Of course if we could do everything at once, we wouldn't >> be worrying about balancing the short term with the long. But, >> delaying further pattern matching progress until we have a full >> story for declared patterns seemed a bit extreme. >> >> So it's not that we missed that route -- indeed, that's the route >> that got us to the current position -- it's just that route was >> rejected as "would delay delivering real value now." >> >> >> -------- Forwarded Message -------- >> Subject: Re: Two new draft pattern matching JEPs >> Date: Thu, 4 Mar 2021 17:34:15 +0100 >> From: Victor Nazarov >> >> To: amber-spec-comments at openjdk.java.net >> >> >> >> >> Hello Java experts, >> >> I've been following the discussion about new JEPs for pattern >> matching and >> I've observed a controversy considering the introduction of >> Pattern guards. >> >> It seems that what Brian Goetz stated as a problem is: >> >>> * either we >>> don't provide a way to write guarded patterns now (which is not a >>> problem for instanceof, but is for switch), or >>> * we nail some bit of terrible syntax onto the switch that we're stuck >> with. >> >> But from my understanding this misses another route: >> >>> We've already discussed how some patterns (e.g., regex) will take input >>> arguments, which are expressions. We haven't quite nailed down our >>> syntactic conventions for separating input expressions from output >>> bindings, but the notion of a pattern that accepts expressions as input >>> is most decidedly not outside the model. >> >> When patterns with arguments become available users are able to >> write code >> like the following (with imaginary syntax). >> >> ```` >> String s = "aabb"; >> String result = switch (s) { >> case String.["(a+)(b+)"]matches(var as, var bs) -> bs + as; >> default -> "no match"; >> } >> ```` >> >> Having this ability nothing prevents users to define a `guard` >> pattern in >> their library and to use it like: >> >> ```` >> case Rectangle(Point x, Point y) & Utils.[x > 0 && y > 0]guard() >> ```` >> >> For me it seems a good solution to introduce a more general mechanism >> (patterns with input arguments) and use it to define a library >> `guard` >> pattern then to nail some additional syntax (true/false overload). >> >> So returning to the original problem then I think a possible >> solution is to >> introduce some special `guard` library pattern right away. >> >> Cons: >> * Need to decide on special syntax for input arguments right away >> * Hard to specify that custom patterns with input arguments are >> not yet >> available and only special library `guard` patterns can use this >> feature. >> >> Pros: >> * Less special syntax in the language, because input arguments >> are going >> to be introduced anyway >> * It is probably easier to explain to users the usefulness of `&` >> because >> that way users can already see that not only destructuring >> pattern are >> going to be available, but more generic and complex patterns with >> input >> arguments are going to be available. >> >> -- >> Victor Nazarov > From maurizio.cimadamore at oracle.com Fri Mar 5 12:48:23 2021 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 5 Mar 2021 12:48:23 +0000 Subject: Two new draft pattern matching JEPs In-Reply-To: References: Message-ID: <11cfb3da-7204-8a8d-0d08-7dc2fba575a1@oracle.com> Looks good - some comments below (I apologize if some of these have been raised elsewhere): ## Patterns in switch * In the general intro, maybe you could link this: https://bugs.openjdk.java.net/browse/JDK-4032356 or this: https://bugs.openjdk.java.net/browse/JDK-7127722 Which are all instances where users wished they had a more general way of switching on their hands (some of these are old, some of these were triggered, predictably, after Java 7 added support for String in switch). * the syntax `case Foo, null` is nice, but the JEP doesn't say what happens if you use that twice - there seems to be an aspect of non-determinism if multiple case labels like these are alllowed? Ah - I see - it's in the well-formedness section. I think a fly-by comment on this would be helpful in the general description, as otherwise this feature looks more general than it is (e.g. it almost looks like a ?String type, which I know it's not). * New pattern forms (e.g. guards) - this nicely complements the feature set described in the JEP - but I guess it's worth considering if guards shouldn't live in their own JEP (e.g. use same approach we followed for array and record patterns), as I can imagine a lot of use cases covered even w/o guards (simple type cases statement such as the one mentioned in the JBS issue above). * The fact that you can't mix between patterns and constants in a switch seems to belong more in well-formedness than in "Enhanced type checking of switch" ? * "The possibility of falling through a switch label that declares a pattern variable must be excluded as a compile-time error." - again, this seem well-formedness? I understand that the discussion is very tied to scoping - but the issue I have with the JEP as it stands is that I'd expect to find, inside the "well-formedness" section a comprehensive set of rules which tell me what I can and cannot write - right now it just contains a _subset_ of all the rules, which are then defined in different sections. So take this mostly as an editorial comment. * Future work - shouldn't this mention the other JEP, rather than speaking vaguely of "deconstruction patterns" ? ## Record Patterns and Array Patterns * Array patterns; It seems like we could do more here. One issue is that we can only match the beginning of an array - fine, maybe we'll add more in the future to deal with this. The other, more important (IMHO) issue, is that the language already gives Iterable a special treatment in for each loop. That is you can use an enhanced for loop using an array source, or an Iterable source and they both work. Here we give arrays precedence, even though in reality there's a lot of code which will just be using collection. That said, I believe one of the impediment to have an Iterable-variant is associated with the fact that there's no syntax to appeal to (and we don't want to make a new one until we have a story for collection literals). In other words, maybe this is all we can do and for good reasons, but maybe the JEP should clarify some of the "why nots" here? * Future work - I think here we should mention a way to declare a local variable as a pattern, without an if/switch - e.g. __syntaxbikeshed Point(var x, var y) = point; Cheers Maurizio On 18/02/2021 12:33, Gavin Bierman wrote: > Dear all, > > The next steps in adding pattern matching to Java are coming! I have drafted two new JEPs: > > - Nested Record and Array Patterns: https://bugs.openjdk.java.net/browse/JDK-8260244 > > - Pattern Matching for switch: https://bugs.openjdk.java.net/browse/JDK-8213076 > > We split them up to try to keep the complexity down, but we might decide to merge them into a single JEP. Let me know what you think. > > Draft language specs are under way - I will announce those as soon as they are ready. > > Comments welcome as always! > > Thanks, > Gavin From brian.goetz at oracle.com Fri Mar 5 16:29:17 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 5 Mar 2021 11:29:17 -0500 Subject: [External] : Re: Fwd: Two new draft pattern matching JEPs In-Reply-To: References: <4e038546-59d6-d6d7-e4cc-5c32a990b656@oracle.com> <0a7c77fa-a5cb-df2f-543e-c0ae297ac6da@oracle.com> Message-ID: <013b18b6-1daf-6e34-5b6b-36fc80d9e090@oracle.com> > > The one objection I still have to grobble is one I've raised before: > it makes it hard to imagine ever representing disjunctions as guards. > I always bring up stuff like > > switch (people) { > ? case Pair(Person(String name1, int age1), Person(String name2, int > age2)) > ? ? | age1 > age2 -> name1?+ " is older" > ? ? | age2 > age1 -> name2?+ " is older" > ? ? | otherwise -> format("%s and %s are the same age", name1, name2) > } > If we can assume that such "sub-switches" are routinely total on the top pattern, I think we can do this with the tools on the table, perhaps with some small adjustments. Note that for deconstruction patterns, you can bind to the top-level result.? This means you can further switch on it.? If we can use grobble(e) as a case label without a pattern (which makes sense under either the interpretation that grobble(e) is a pattern, or that it is a feature of case), we can write your above as: ??? switch (people) { ? ? ? case Pair(Person(String name1, int age1), Person(String name2, int age2)) p -> ????????? switch (p) { ????????????? case grobble(age1 > age2) -> name1?+ " is older"; ? ? ? ??????? case grobble(age2 > age1) -> name2?+ " is older"; ? ??????????? default -> format("%s and %s are the same age", name1, name2); ????????? } ??? } which isn't too bad.? Sometimes we will want to further match on `p` (if I was factoring cases P&Q and P&R), but in this case I'm just factoring guards so the use of `p` here is mostly noise.? We could consider allowing you to drop the switch operand and rewrite as ??? switch (people) { ? ? ? case Pair(Person(String name1, int age1), Person(String name2, int age2)) -> ????????? switch? { ????????????? case grobble(age1 > age2) -> name1?+ " is older"; ? ? ? ??????? case grobble(age2 > age1) -> name2?+ " is older"; ? ??????????? default -> format("%s and %s are the same age", name1, name2); ????????? } ??? } in that case.? A switch with no target can only contain grobble cases; perhaps in that "case" we allow you to elide the grobbling: ??? switch (people) { ? ? ? case Pair(Person(String name1, int age1), Person(String name2, int age2)) -> ????????? switch { ????????????? case age1 > age2 -> name1?+ " is older"; ? ? ? ??????? case age2 > age1 -> name2?+ " is older"; ? ??????????? default -> format("%s and %s are the same age", name1, name2); ????????? } ??? } All of this is switch-golf, but the point is that the use case of factoring P&Q and P&R is one that is handled reasonably well by nested switches already, if we had a functioning switch guard mechanism and our sub-switches are total.? If the sub-switches are total, you'd need a real guard to capture the union of the guards, which might not be so great, but I suspect (based on the fact that this idiom comes from Haskell, and most piecewise decompositions in Haskell end with an otherwise) that this is more of a corner case. From brian.goetz at oracle.com Fri Mar 5 19:14:01 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 5 Mar 2021 14:14:01 -0500 Subject: Guards Message-ID: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> Let me try and summarize all that has been said on the Guards topic. #### Background and requirements For `instanceof`, we don't need any sort of guard right now (with the patterns we have); we can already conjoin arbitrary boolean expressions with `&&` in all the contexts we can use `instanceof`, because it's a boolean expression.? (This may change in the future as patterns get richer.)? So we can already express our canonical guarded Point example with ??? if (p instanceof Point(var x, var y) && x > y) { ... } with code that no one will find confusing. For switch, we can't do this, because case labels are not boolean expressions, they're some ad-hoc sub-language.? When the sub-language was so limited that it could only express int and string constants, this wasn't a problem; there was little refinement needed on `case "Foo"`. As we make switch more powerful, we face a problem: if the user drifts out of the territory of what can be expressed as case labels, they fall off the cliff and have to refactor their 50-way switch into an if-else chain.? This will be a really bad user experience.? Some sort of escape hatch to boolean logic buys us insurance against this bad experience -- as long as you can express your non-pattern criteria with a boolean expression (which is pretty rich), you don't have to leave switch-land. So we took as our requirement: ??? Some sort of guard construct that is usable in switch is a forced move. #### Expressing guards in switch There are several ways to envision guards: ?- As patterns that refine other patterns (e.g., a "true" pattern) ?- As an additional feature of "case" in switch (e.g., a "when" clause) ?- As an imperative control-flow statement usable in "switch" (e.g., "continue") We've largely rejected the third (even though it is more primitive than the others), because we think the resulting code will be much harder to read and more error-prone.? We've bounced back and forth between "let's nail something on the side of switch" and "let's let the rising pattern tide lift all conditional constructs." Other languages have demonstrated that guards in switch-like constructs are viable. The argument in favor of nailing something on the side of switch is that it is pragmatic; it is immediately understandable, it raises the expressivity of `switch` to where `if` already is, and it solves the immediate requirement we have in adding patterns to switch. The argument against is that it is not a primitive; it is dominated by the option of making patterns richer (by adding boolean patterns), it is weak and non-compositional, and overly specific to switch.? (It is possible to make worse-is-better arguments here that we should do this anyway, but it's not really possible to seriously claim better, despite attempts to the contrary.) #### Interpreting the feedback The JEP proposes a powerful and compositional approach: ?- true/false patterns that accept arbitrary boolean expressions (and which ignore their target); ?- combining patterns with a pattern-AND combinator On the one hand, this is a principled, orthogonal, compositional, expressive, broadly applicable approach, based on sensible primitives, which will be usable in other contexts, and which anticipate future requirements and directions. On the other hand, there has been a pretty powerful emotional reaction, which could be summarized as "sorry, we're not ready for this degree of generality yet with respect to patterns." This emotional reaction seems to have two primary components: ?- A "who moved my cheese" reaction to the overloading of `true` in this way -- that `true` seems to be, in everyone's mind, a constant, and seeing it as a pattern is at least temporarily jarring.? (This may be a temporary reaction, but there's still a cost of burning through it.) ?- A reaction to "borrowing & from the future" -- because the other use cases for &-composition are not obvious or comfortable yet, the use of &-composition seems foreign and forced, and accordingly engenders a strong reaction. The former (which I think is felt more acutely) could be addressed by taking a conditional keyword such as `when` here; ad-hoc "focus" research suggests the negative reaction here is lower, but still there. The latter is, I think, the more linguistically significant of the two; even though there is a strong motivation for & coming down the pike, this is not the gentle introduction to pattern combination that we'd like, and developer's mental models of patterns may not be ready.? Patterns are still new, and we'd like for the initial experience to make people want more, rather than scare them with too much up front. #### Options I suspect that we'd get a lot of mileage out of just renaming true to something like "when"; it avoids the "but that's not what true is" reaction, and is readable enough: ??? case Foo(var x) & when(x > 0): but I think it will still be perceived as "glass half empty", with lots of "why do I need the &" reactions.? And, in the trivial (but likely quite common, at least initially) case of one pattern and one guard, the answers are not likely to be very satisfying, no matter how solidly grounded in reality, because the generality of the compositional approach is not yet obvious enough to those seeing patterns for the first time. I am not compelled by the direction of "just add guards to switch and be done with it", because that's a job we're going to have to re-do later.? But I think there's a small tweak which may help a lot: do that job now, with only a small shadow of lasting damage: ?- Expose `grobble(expr)` clauses as an option on pattern switch cases; ?- When we introduce & combination (which can be deferred if we have a switch guard now), plan for a `grobble(e)` pattern. At that point, ??? case Foo(var x) grobble(x > 0): is revealed to be sugar for ??? case Foo(var x) & grobble(x > 0): As as bonus, we can use grobble by itself in pattern switches to incorporate non-target criteria: ??? case grobble(e): which is later revealed to be sugar for: ??? case Foo(var _) & grobble(e): The downside here is that in the long run, we have something like the C-style array declarations; in the trivial case of a single pattern with a guard, you can leave in the & or leave it out, not unlike declaring `int[] x` vs `int x[]`. Like the "transitional" (but in fact permanent) sop of C-style declarations, the "optional &" will surely become an impediment ("why can I leave it out here, but not there, that's inconsistent"). All that said, this is probably an acceptable worse-is-better direction, where in the short term users are not forced to confront a model that they don't yet understand (or borrow concepts from the future), with a path to sort-of-almost-unification in the future that is probably acceptable. From maurizio.cimadamore at oracle.com Fri Mar 5 22:11:31 2021 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 5 Mar 2021 22:11:31 +0000 Subject: Guards In-Reply-To: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> Message-ID: <7fd8ebdb-04d0-ea56-d7de-7590099b4f6d@oracle.com> This seems like a nice landing. The unification of guards and AND patterns was clever, and clearly compositional, but exposing AND patterns just to get to guards can seem a daunting step. I agree that the priority, language design-wise is to get the combo switch/case and if/instanceof on roughly the same expressive footing. Without _some_ kind of guard-like capabilities, doing patterns in switches is gonna be very limited, and some code will necessarily fall off the expressiveness cliff and be rewritten as an if/else chain. I think the `when(expr)` syntax is a minor detour; it's easy on the eye and, as you show, has a gentle progression as to where we are headed. Personally, given how common the basic guard use case is, I don't mind a little bit of sugar sprinkled here and there, even though yes, it does create two ways to do the same thing. Cheers Maurizio On 05/03/2021 19:14, Brian Goetz wrote: > Let me try and summarize all that has been said on the Guards topic. > > #### Background and requirements > > For `instanceof`, we don't need any sort of guard right now (with the > patterns we have); we can already conjoin arbitrary boolean > expressions with `&&` in all the contexts we can use `instanceof`, > because it's a boolean expression. (This may change in the future as > patterns get richer.)? So we can already express our canonical guarded > Point example with > > ??? if (p instanceof Point(var x, var y) && x > y) { ... } > > with code that no one will find confusing. > > For switch, we can't do this, because case labels are not boolean > expressions, they're some ad-hoc sub-language.? When the sub-language > was so limited that it could only express int and string constants, > this wasn't a problem; there was little refinement needed on `case > "Foo"`. > > As we make switch more powerful, we face a problem: if the user drifts > out of the territory of what can be expressed as case labels, they > fall off the cliff and have to refactor their 50-way switch into an > if-else chain.? This will be a really bad user experience.? Some sort > of escape hatch to boolean logic buys us insurance against this bad > experience -- as long as you can express your non-pattern criteria > with a boolean expression (which is pretty rich), you don't have to > leave switch-land. > > So we took as our requirement: > > ??? Some sort of guard construct that is usable in switch is a forced > move. > > #### Expressing guards in switch > > There are several ways to envision guards: > > ?- As patterns that refine other patterns (e.g., a "true" pattern) > ?- As an additional feature of "case" in switch (e.g., a "when" clause) > ?- As an imperative control-flow statement usable in "switch" (e.g., > "continue") > > We've largely rejected the third (even though it is more primitive > than the others), because we think the resulting code will be much > harder to read and more error-prone.? We've bounced back and forth > between "let's nail something on the side of switch" and "let's let > the rising pattern tide lift all conditional constructs." > > Other languages have demonstrated that guards in switch-like > constructs are viable. > > The argument in favor of nailing something on the side of switch is > that it is pragmatic; it is immediately understandable, it raises the > expressivity of `switch` to where `if` already is, and it solves the > immediate requirement we have in adding patterns to switch. > > The argument against is that it is not a primitive; it is dominated by > the option of making patterns richer (by adding boolean patterns), it > is weak and non-compositional, and overly specific to switch.? (It is > possible to make worse-is-better arguments here that we should do this > anyway, but it's not really possible to seriously claim better, > despite attempts to the contrary.) > > #### Interpreting the feedback > > The JEP proposes a powerful and compositional approach: > > ?- true/false patterns that accept arbitrary boolean expressions (and > which ignore their target); > ?- combining patterns with a pattern-AND combinator > > On the one hand, this is a principled, orthogonal, compositional, > expressive, broadly applicable approach, based on sensible primitives, > which will be usable in other contexts, and which anticipate future > requirements and directions. > > On the other hand, there has been a pretty powerful emotional > reaction, which could be summarized as "sorry, we're not ready for > this degree of generality yet with respect to patterns." This > emotional reaction seems to have two primary components: > > ?- A "who moved my cheese" reaction to the overloading of `true` in > this way -- that `true` seems to be, in everyone's mind, a constant, > and seeing it as a pattern is at least temporarily jarring.? (This may > be a temporary reaction, but there's still a cost of burning through it.) > > ?- A reaction to "borrowing & from the future" -- because the other > use cases for &-composition are not obvious or comfortable yet, the > use of &-composition seems foreign and forced, and accordingly > engenders a strong reaction. > > The former (which I think is felt more acutely) could be addressed by > taking a conditional keyword such as `when` here; ad-hoc "focus" > research suggests the negative reaction here is lower, but still there. > > The latter is, I think, the more linguistically significant of the > two; even though there is a strong motivation for & coming down the > pike, this is not the gentle introduction to pattern combination that > we'd like, and developer's mental models of patterns may not be > ready.? Patterns are still new, and we'd like for the initial > experience to make people want more, rather than scare them with too > much up front. > > #### Options > > I suspect that we'd get a lot of mileage out of just renaming true to > something like "when"; it avoids the "but that's not what true is" > reaction, and is readable enough: > > ??? case Foo(var x) & when(x > 0): > > but I think it will still be perceived as "glass half empty", with > lots of "why do I need the &" reactions.? And, in the trivial (but > likely quite common, at least initially) case of one pattern and one > guard, the answers are not likely to be very satisfying, no matter how > solidly grounded in reality, because the generality of the > compositional approach is not yet obvious enough to those seeing > patterns for the first time. > > I am not compelled by the direction of "just add guards to switch and > be done with it", because that's a job we're going to have to re-do > later.? But I think there's a small tweak which may help a lot: do > that job now, with only a small shadow of lasting damage: > > ?- Expose `grobble(expr)` clauses as an option on pattern switch cases; > > ?- When we introduce & combination (which can be deferred if we have a > switch guard now), plan for a `grobble(e)` pattern.? At that point, > > ??? case Foo(var x) grobble(x > 0): > > is revealed to be sugar for > > ??? case Foo(var x) & grobble(x > 0): > > As as bonus, we can use grobble by itself in pattern switches to > incorporate non-target criteria: > > ??? case grobble(e): > > which is later revealed to be sugar for: > > ??? case Foo(var _) & grobble(e): > > The downside here is that in the long run, we have something like the > C-style array declarations; in the trivial case of a single pattern > with a guard, you can leave in the & or leave it out, not unlike > declaring `int[] x` vs `int x[]`.? Like the "transitional" (but in > fact permanent) sop of C-style declarations, the "optional &" will > surely become an impediment ("why can I leave it out here, but not > there, that's inconsistent"). > > All that said, this is probably an acceptable worse-is-better > direction, where in the short term users are not forced to confront a > model that they don't yet understand (or borrow concepts from the > future), with a path to sort-of-almost-unification in the future that > is probably acceptable. > > From guy.steele at oracle.com Fri Mar 5 22:12:27 2021 From: guy.steele at oracle.com (Guy Steele) Date: Fri, 5 Mar 2021 17:12:27 -0500 Subject: Guards In-Reply-To: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> Message-ID: <7F3E367A-141B-4A1E-8837-4C7A27BACD0F@oracle.com> Thanks for this summary, Brian. But there is just one place where the argument involves a perhaps unnecessary overcommitment. See below. > On Mar 5, 2021, at 2:14 PM, Brian Goetz wrote: > > Let me try and summarize all that has been said on the Guards topic. > > #### Background and requirements > > For `instanceof`, we don't need any sort of guard right now (with the patterns we have); we can already conjoin arbitrary boolean expressions with `&&` in all the contexts we can use `instanceof`, because it's a boolean expression. (This may change in the future as patterns get richer.) So we can already express our canonical guarded Point example with > > if (p instanceof Point(var x, var y) && x > y) { ... } > > with code that no one will find confusing. > > For switch, we can't do this, because case labels are not boolean expressions, they're some ad-hoc sub-language. When the sub-language was so limited that it could only express int and string constants, this wasn't a problem; there was little refinement needed on `case "Foo"`. > > As we make switch more powerful, we face a problem: if the user drifts out of the territory of what can be expressed as case labels, they fall off the cliff and have to refactor their 50-way switch into an if-else chain. This will be a really bad user experience. Some sort of escape hatch to boolean logic buys us insurance against this bad experience -- as long as you can express your non-pattern criteria with a boolean expression (which is pretty rich), you don't have to leave switch-land. > > So we took as our requirement: > > Some sort of guard construct that is usable in switch is a forced move. > > #### Expressing guards in switch > > There are several ways to envision guards: > > - As patterns that refine other patterns (e.g., a "true" pattern) A guard construct need not itself be a pattern. Rather, it can be viewed as a map from patterns to patterns. Indeed, they are formulated in exactly that way in Gavin?s BNF in JEP JDK-8213076 "Pattern Matching for switch?: a guard is not a pattern, but can only appear within a pattern as the right-hand operand of `&`: Pattern: PatternOperand Pattern & PatternOperandOrGuard PatternOperandOrGuard: PatternOperand GuardPattern As a result, if we curry and squint, we can see that ?& Guardpattern? is a map from patterns to patterns. We can also see that ?& Pattern? is a map from patterns to patterns; and finally we can appreciate two other points: (1) GuardPattern need not ever actually be regarded as a patterns, and (2) we have overloaded `&` to mean two rather different things. While it is possible to express the fact that a guard construct is a map from patterns to patterns by insisting that a guard is itself a pattern and then using the pattern conjunction operator, this is not the only way to express or model that fact. Now, the quoted BNF has reached its current structure because, as the JEP carefully explains, The grammar has been carefully designed to exclude a guard pattern as a valid top-level pattern. There is little point in writing pattern matching code such as o instanceof true(s.length != 0). Guard patterns are intended to be refine the meaning of other patterns. The grammar reflects this intuition. As a result, a guard necessarily appears to the right of a `&` and therefore necessarily to the right of a pattern. We should also inquire as to whether it is ever desirable in practice, within a chain of `&` (pattern conditional-and) operations for a pattern to appear to the right of a guard. If not, then `&` chains always have the simple form pattern & pattern & ? & pattern & guard & guard & ? & guard where the number of patterns must be positive but the number of guards may be zero. And if this is the case, it is not unreasonable to ask whether readability might not be better served by better marking that transition from patterns to guard in the chain, for example: pattern & pattern & ? & pattern when guard & guard & ? & guard And then we see that there really is no reason to try to overload `&` (however it is actually spelled) to mean both pattern conjunction and guard conjunction, because guard conjunction already exists in the form of the `&&` expression operator: pattern & pattern & ? & pattern when guard && guard && ? && guard and therefore we can, after all, simplify this general form to the case of zero or one guards: pattern & pattern & ? & pattern [when guard] Finally, given that (an earlier version of) the patterns design already encompasses forms that can bind the entire object as well as components (what is done in other languages with `as`, I have to ask: what are the envisioned practical applications of pattern conjunction other than as a cute way to include guards or a (more verbose) way to bind the entire value as well as components? Maybe as a way to fake intersection types? Now, all of this has no bearing on whether or not guards are required to be ?top level only? in all cases; it argues only that guards need not appear within pattern-conjunction chains. But I believe it would be perfectly reasonable to write case Point(int x when x > 0, int y when y > x): R?mi has argued that this would be better written case Point(int x, int y) when x > 0 && y > x: but I would argue that this choice is, and should be, a matter of style, and when matching against a record with many fields it might be more readable to mention each field?s constraint next to its binding rather than to make the reader compare a list of bindings against a list of constraints. Bottom line: there are conceptually three distinct combining forms: pattern conjunction guard conjunction to a pattern, attach a guard and it may be a mistake after all to conflate them by trying to use the same syntax or symbol for all three. So what I would like to see is the convincing application example where you really do want to write pattern & guard & pattern because then everything I?ve written above falls to the ground. But if we cannot come up with such an example, then perhaps what I have written should be examined carefully as a serious design alternative. [more below] > . . . > > #### Options > > I suspect that we'd get a lot of mileage out of just renaming true to something like "when"; it avoids the "but that's not what true is" reaction, and is readable enough: > > case Foo(var x) & when(x > 0): Sorry to bikeshed here, but while ?when? is nice, I think ?if? is even more appealing (short, familiar, already a keyword), especially if it alone can express attachment of a guard to a pattern (and we can argue about whether the parentheses are required): case Foo(var x) if (x > 0): [more below] > but I think it will still be perceived as "glass half empty", with lots of "why do I need the &" reactions. And, in the trivial (but likely quite common, at least initially) case of one pattern and one guard, the answers are not likely to be very satisfying, no matter how solidly grounded in reality, because the generality of the compositional approach is not yet obvious enough to those seeing patterns for the first time. > > I am not compelled by the direction of "just add guards to switch and be done with it", because that's a job we're going to have to re-do later. But I think there's a small tweak which may help a lot: do that job now, with only a small shadow of lasting damage: > > - Expose `grobble(expr)` clauses as an option on pattern switch cases; > > - When we introduce & combination (which can be deferred if we have a switch guard now), plan for a `grobble(e)` pattern. At that point, > > case Foo(var x) grobble(x > 0): > > is revealed to be sugar for > > case Foo(var x) & grobble(x > 0): > > As as bonus, we can use grobble by itself in pattern switches to incorporate non-target criteria: > > case grobble(e): > > which is later revealed to be sugar for: > > case Foo(var _) & grobble(e): I think you meant it is sugar for case var _ & grobble(e): If so, then compare that to the claim that case if (e): is sugar for case var _ if (e): This might be very easy to explain to programmers who want to drop a few if-branches into a switch without having to convert the entire switch into an if-else chain. > The downside here is that in the long run, we have something like the C-style array declarations; in the trivial case of a single pattern with a guard, you can leave in the & or leave it out, not unlike declaring `int[] x` vs `int x[]`. Like the "transitional" (but in fact permanent) sop of C-style declarations, the "optional &" will surely become an impediment ("why can I leave it out here, but not there, that's inconsistent"). > > All that said, this is probably an acceptable worse-is-better direction, where in the short term users are not forced to confront a model that they don't yet understand (or borrow concepts from the future), with a path to sort-of-almost-unification in the future that is probably acceptable. > > From guy.steele at oracle.com Fri Mar 5 22:41:30 2021 From: guy.steele at oracle.com (Guy Steele) Date: Fri, 5 Mar 2021 17:41:30 -0500 Subject: Two new draft pattern matching JEPs In-Reply-To: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> Message-ID: > On Mar 3, 2021, at 7:50 AM, Remi Forax wrote: > > ----- Mail original ----- >> De: "Gavin Bierman" >> ?: "amber-spec-experts" >> Envoy?: Jeudi 18 F?vrier 2021 13:33:20 >> Objet: Two new draft pattern matching JEPs > >> Dear all, >> > > [...] > >> >> - Pattern Matching for switch: https://bugs.openjdk.java.net/browse/JDK-8213076 >> >> We split them up to try to keep the complexity down, but we might decide to >> merge them into a single JEP. Let me know what you think. > > I think that we have got a little over our head with the idea of replacing the switch guard by the guard pattern + conditional-and pattern. > > The draft is poor in explanations on why we should do that apart because it's more powerful, which is true but that not how to evaluate a feature. > Here, it doesn't seem we are trying to fix a broken feature or adapt an existing feature to Java. It's just more powerful, but with a lot of drawbacks, see below. > > My main concern is when mixing the deconstructing pattern with the guard + and pattern, those twos (two and a half) doesn't mix well. > > For a starter, at high level, the idea is to mix patterns and expressions (guards are boolean expressions), but at the same time, we have discussed several times to not allow constants inside patterns to make a clear distinction between patterns and expressions. We have a inconsistency here. > > The traditional approach for guards cleanly separate the pattern part from the expression part > case Rectangle(Point x, Point y) if x > 0 && y > 0 > which makes far more sense IMO. > > The current proposal allows > case Rectangle(Point x & true(x > 0), Point y & true(y > 0)) > which is IMO far least readable because the clean separation between the patterns and the expressions is missing. As I have already indicated in another email, if Rectangle had six or eight components rather than just two, for some purposes it might be more readable to have the constraint for each component listed next to its binding, rather than making the reader compare a long list of bindings to a long list of constraints. [more below] > There is also a mismatch in term of evaluation, an expression is evaluated from left to right, for a pattern, you have bindings and bindings are all populated at the same time by a deconstructor, this may cause issue, by example, this is legal in term of execution > case Rectangle(Point x & true(x > 0 && y > 0), Point y) > because at the point where the pattern true(...) is evaluated, the Rectangle has already been destructured, obviously, we can ban this kind of patterns to try to conserve the left to right evaluation but the it will still leak in a debugger, you have access to the value of 'y' before the expression inside true() is called. I would like to question your assertion bindings are all populated at the same time by a deconstructor Is this really necessarily true? I would have thought that the job of the deconstructor is to provide the values for the bindings, and that int principle the values are then kept in anonymous variables or locations while the subpatterns are processed, one by one, from left to right. Because consider a more complex pattern: case Rectangle(Point(int x1, int y1), Point(int x2, int y2)) I would expect that the deconstructor for Rectangle does not fill in all four variables x1, y1, x2, y2 all at once; rather, it just supplies two values that are points, and then the first point value is matched against pattern Point(int x1, int y1), and only then is the second point value matched against pattern Point(int x2, int y2)). Now this example is not exactly analogous to your original, because we have not provided explicit variables for this purpose. I believe that in an earlier version of the design one would write case Rectangle(Point(int x1, int y1), Point(int x2, int y2)) But perhaps in the current proposal one must write case Rectangle(Point(int x1, int y1) & var p1, Point(int x2, int y2) & var p2) or perhaps case Rectangle(var p1 & Point(int x1, int y1), var p2 & Point(int x2, int y2)) In all of these cases, my argument is still the same: the simplest model is that that deconstructor for Rectangle just supplies two values that are points, and then the first point value is matched against the first sub pattern, and only then is the second point value matched against the second subpattern. As a result p2 and x2 and y2 do not yet have bindings or values while the first sub-pattern is being matched. A compiler would likely optimize common special cases to effectively implement all-at-once population of bindings when it would be impossible to detect any difference in behavior. But I don?t think all-at-once population is the right theoretical model. [more below] > In term of syntax, currently the parenthesis '(' and ')' are used to define/destructure the inside, either by a deconstructor or by a named pattern, but in both cases the idea is that it's describe the inside. Here, true() and false() doesn't follow that idea, there are escape mode to switch from the pattern world into the expression world. > At least we can use different characters for that. > > Also in term of syntax again, introducing '&' in between patterns overloads the operator '&' with one another meaning, my students already have troubles to make the distinction between & and && in expressions. > As i already said earlier, and this is also said in the Python design document, we don't really need an explicit 'and' operator in between patterns because there is already an implicit 'and' between the sub-patterns of a deconstructing pattern. As I have already indicated in another email, I agree with you here; I very much share your concerns about the overloading of a single symbol `&` (or whatever spelling we give it) to mean two or three different things within patterns, not to mention its existing uses in expression contexts. > To finish, there is also an issue with the lack of familiarity, when we have designed lambdas, we have take a great care to have a syntax similar to the C#, JS, Scala syntax, the concept of guards is well known, to introduce a competing feature in term of syntax and semantics, the bar has to be set very high because we are forcing people to learn a Java specific syntax, not seen in any other mainstream languages*. > > For me, the cons far outweigh the pro(s) here, but perhaps i've missed something ? > >> >> Draft language specs are under way - I will announce those as soon as they are >> ready. >> >> Comments welcome as always! > > regards, > >> >> Thanks, >> Gavin > > R?mi > * Java do not invent things, it merely stole ideas from the other and make them its own in a coherent way From john.r.rose at oracle.com Fri Mar 5 23:08:18 2021 From: john.r.rose at oracle.com (John Rose) Date: Fri, 5 Mar 2021 15:08:18 -0800 Subject: Guards In-Reply-To: <7F3E367A-141B-4A1E-8837-4C7A27BACD0F@oracle.com> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <7F3E367A-141B-4A1E-8837-4C7A27BACD0F@oracle.com> Message-ID: <4235D960-D421-45DF-B725-40D7584945CF@oracle.com> Guy, I think you are right that guards have a natural home on the right-hand edge of patterns (whether the patterns are simple P or composite P&Q, etc). And because patterns can nest, this ?right edge? can occur placed inside a sub-pattern, which lets us smuggle guards anywhere we like, with a little care: P&(var x when g(x))&Q. If we double down on this position (which I think is tenable), then we have another opportunity: The right edge of a pattern case label and the right edge of an instanceof pattern are both places where ?g(x) && h(x)? is a natural place to look for more conditional logic. So I suggest there is a privileged position for guard sugar that looks like ?stuff that follows instanceof?. That is: switch ? case P && g(x): ?instanceof P && g(x) I.e, let?s spell ?when? as ?&&?. Then the right edge of a pattern, wherever it may roam, will be the place your eye will look for guards, in the form of immedately following && G sequences. This is not just consistency for consistency?s sake, I think, but a real gain in recognizability of patterns. (It?s also a sneaky hack, since the ?&&? doesn?t show up in the pattern grammar, but rather appears twice, once in the regular expression grammar, and once in *some* of the grammatical places where a pattern can occur. But it is a harmless, even useful sneak.) Where does this leave the single ?&? operator? Where it always was: It can be a feature added in the future. In fact, ?P&Q? would be sugar for ?var t2 && t2 instanceof P && t2 instanceof Q?. Perhaps && is the right primitive, as well as an easy-to-recognize syntax. More below: On Mar 5, 2021, at 2:12 PM, Guy Steele wrote: > > Thanks for this summary, Brian. But there is just one place where the argument involves a perhaps unnecessary overcommitment. See below. > >> On Mar 5, 2021, at 2:14 PM, Brian Goetz wrote: >> >> ... Some sort of guard construct that is usable in switch is a forced move. >> >> #### Expressing guards in switch >> >> There are several ways to envision guards: >> >> - As patterns that refine other patterns (e.g., a "true" pattern) > > A guard construct need not itself be a pattern. Rather, it can be viewed as a map from patterns to patterns. Indeed, they are formulated in exactly that way in Gavin?s BNF in JEP JDK-8213076 "Pattern Matching for switch?: a guard is not a pattern, but can only appear within a pattern as the right-hand operand of `&`: > > Pattern: > PatternOperand > Pattern & PatternOperandOrGuard > PatternOperandOrGuard: > PatternOperand > GuardPattern > > As a result, if we curry and squint, we can see that ?& Guardpattern? is a map from patterns to patterns. We can also see that ?& Pattern? is a map from patterns to patterns; and finally we can appreciate two other points: (1) GuardPattern need not ever actually be regarded as a patterns, and (2) we have overloaded `&` to mean two rather different things. Yes. There are parsing problems if we try to take such an overload seriously. I think we need two kinds of ?&?, one which says ?stay in pattern land? and one which says ?here?s an expression?. Another way to factor this would be an explicit marker (such as ?true? was proposed to be) which says, ?here?s an expression, just this one place?. P & Q P with G P && G P & __Expr G > While it is possible to express the fact that a guard construct is a map from patterns to patterns by insisting that a guard is itself a pattern and then using the pattern conjunction operator, this is not the only way to express or model that fact. > > Now, the quoted BNF has reached its current structure because, as the JEP carefully explains, > > The grammar has been carefully designed to exclude a guard pattern as a valid > top-level pattern. There is little point in writing pattern matching code such as > o instanceof true(s.length != 0). Guard patterns are intended to be refine > the meaning of other patterns. The grammar reflects this intuition. > > As a result, a guard necessarily appears to the right of a `&` and therefore necessarily to the right of a pattern. We should also inquire as to whether it is ever desirable in practice, within a chain of `&` (pattern conditional-and) operations for a pattern to appear to the right of a guard. If not, then `&` chains always have the simple form > > pattern & pattern & ? & pattern & guard & guard & ? & guard > > where the number of patterns must be positive but the number of guards may be zero. And if this is the case, it is not unreasonable to ask whether readability might not be better served by better marking that transition from patterns to guard in the chain, for example: > > pattern & pattern & ? & pattern when guard & guard & ? & guard > > And then we see that there really is no reason to try to overload `&` (however it is actually spelled) to mean both pattern conjunction and guard conjunction, because guard conjunction already exists in the form of the `&&` expression operator: > > pattern & pattern & ? & pattern when guard && guard && ? && guard s/when/&&/ is my proposal; then &&guard is the visual cue, and you can stack them. (If you want || or ?:, you will need to use parens.) > > and therefore we can, after all, simplify this general form to the case of zero or one guards: > > pattern & pattern & ? & pattern [when guard] > > Finally, given that (an earlier version of) the patterns design already encompasses forms that can bind the entire object as well as components (what is done in other languages with `as`, I have to ask: what are the envisioned practical applications of pattern conjunction other than as a cute way to include guards or a (more verbose) way to bind the entire value as well as components? Maybe as a way to fake intersection types? > > Now, all of this has no bearing on whether or not guards are required to be ?top level only? in all cases; it argues only that guards need not appear within pattern-conjunction chains. But I believe it would be perfectly reasonable to write > > case Point(int x when x > 0, int y when y > x): > > R?mi has argued that this would be better written > > case Point(int x, int y) when x > 0 && y > x: > > but I would argue that this choice is, and should be, a matter of style, and when matching against a record with many fields it might be more readable to mention each field?s constraint next to its binding rather than to make the reader compare a list of bindings against a list of constraints. I agree. Having a less restrictive language lets bad coders code badly, but lets the rest of us code more clearly. > ? > So what I would like to see is the convincing application example where you really do want to write > > pattern & guard & pattern > > because then everything I?ve written above falls to the ground. Not completely: You can write one of: pattern & (var p && guard(p)) & pattern pattern & (var _ && guard) & pattern > But if we cannot come up with such an example, then perhaps what I have written should be examined carefully as a serious design alternative. I think you are on the right track. BTW, we may have a need in two places for syntax which escapes from pattern syntax. Patterns and expressions are distinct sub-languages, and sometimes we want to nest one inside the other. The ?instanceof? operator escapes from expression land to pattern land. The ?&&? or ?with? or ?if? operator escapes from pattern land to expression land, permanently at the right edge. We will also need an escape which marks in-args to static patterns, as ?withMapping(? inarg, var outarg)?. ? John From brian.goetz at oracle.com Fri Mar 5 23:11:54 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 5 Mar 2021 18:11:54 -0500 Subject: Guards In-Reply-To: <7F3E367A-141B-4A1E-8837-4C7A27BACD0F@oracle.com> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <7F3E367A-141B-4A1E-8837-4C7A27BACD0F@oracle.com> Message-ID: > A guard construct need not itself be a pattern. True.? What is minimally needed is a _syntactic_ separation of what is pattern and what is expression, without having to wait for semantic analysis to understand what is being combined.? This is, in part, because there are still sequences that are matched by both the pattern and expression productions, notably `Identifier()` (could be a deconstruction pattern with no bindings, or could be a method invocation.) > Rather, it can be viewed as a map from patterns to patterns. ?Indeed, > they are formulated in exactly that way in Gavin?s BNF in JEP > JDK-8213076 "Pattern Matching for switch?: a guard is not a pattern, > but can only appear within a pattern as the right-hand operand of `&`: > > Pattern: > PatternOperand > Pattern?&?PatternOperandOrGuard > PatternOperandOrGuard: > PatternOperand > GuardPattern > As a result, a guard necessarily appears to the right of a `&` and > therefore necessarily to the right of a pattern. ?We should also > inquire as to whether it is ever desirable in practice, within a chain > of `&` (pattern conditional-and) operations for a pattern to appear to > the right of a guard. I have long had a nagging feeling that this will eventually be desirable.? Let's say we have P & g & Q & h; under what conditions can we commute g and Q without regret?? I can think of four potential sources of regret: ?- g declares bindings that are inputs to Q ?- the cost model of Q is such that we'd like to run g first, and short-circuit ?- Q might throw an exception when g does not hold ?- Q might have side-effects that we don't want to run if g does not hold I think we can eliminate the last one; I'm pretty comfortable saying that if you write side effects in pattern declarations, you get what you deserve.? And, the linguistic part of patterns are not supposed to throw exceptions, but badly written pattern declarations may anyway.? But that still leaves the dataflow and performance concerns; I think I will eventually want to be able to specify the order, and get short-circuiting.? This is why I've resisted this direction to date. > If not, then `&` chains always have the simple form > > pattern & pattern & ? & pattern & guard & guard & ? & guard > > where the number of patterns must be positive but the number of guards > may be zero. ?And if this is the case, it is not unreasonable to ask > whether readability might not be better served by better marking that > transition from patterns to guard in the chain, for example: > > pattern & pattern & ? & pattern when guard & guard & ? & guard > > And then we see that there really is no reason to try to overload `&` > (however it is actually spelled) to mean both pattern conjunction and > guard conjunction, because guard conjunction already exists in the > form of the `&&` expression operator: > > pattern & pattern & ? & pattern when?guard && guard && ? && guard > > and therefore we can, after all, simplify this general form to the > case of zero or one guards: > > pattern & pattern & ? & pattern?[when?guard] There's one more turn of this crank: if we are willing to move the guards all to the right (big if), then, why say `when`, and not `&&`?? Then it looks just like the `if...instanceof` situation. ??? case pattern & pattern && guard: This further align patterns in instanceof with patterns in switch. (With one potentially surprising caveat: we can never switch on booleans; `case true && false` would not match `true`. Pause for groans.) > Finally, given that (an earlier version of) the patterns design > already encompasses forms that can bind the entire object as well as > components (what is done in other languages with `as`, ?I have to ask: > what are the envisioned practical applications of pattern conjunction > other than as a cute way to include guards or a (more verbose) way to > bind the entire value as well as components? ?Maybe as a way to fake > intersection types? When a pattern focuses on a part, rather than the whole.? A pattern like `Point(var x, var y)` matches / destructures the whole thing, but other patterns can act as queries.? Imagine we have a pattern `Map.with(key)(var value)`, which matches maps that have the specified key.? We would likely want to combine these with &: ??? if (x instanceof (Map.with(key1)(var val1) & Map.with(key2)(var val2))) { ... } This scales up to query APIs such as a JSON parsing API, where you only want to match a blob of JSON if it has all the parts you are looking for (similar to "spec/conform" in Clojure.)? From https://github.com/openjdk/amber-docs/blob/master/site/design-notes/pattern-match-object-model.md: switch (doc) { ??? case stringKey("firstName")(var first) ???????? & stringKey("lastName")(var last) ???????? & intKey("age")(var age) ???????? & objectKey("address")( ???????????????? stringKey("city")(var city) ???????????????? & stringKey("state")(var state) ???????????????? & ...): ... } This expresses not only the all-or-nothing nature of the composite query, but permits the pattern to match the structure of what is being queried (the `objectKey` pattern has a nested pattern which applies to the body of that object, which itself is an & pattern. This is the sort of example I could imagining wanting to stick a guard in the middle of; I could well want to guard "name not empty" and not bother parsing the rest of the document.? Semantically that might be equivalent to putting the guard at the end, but the user might not thank us for not letting them short-circuit out. The real unknown is: ??? - g declares bindings that are inputs to Q Can we construct a credible example? As to intersection types, pattern-& is not even the right vehicle, because in ??? case Foo f & Bar b: then f and b will have types Foo and Bar, but really, I want something of type (Foo&Bar).? If this were important, I'd probably want to be able to use an intersection type in the type pattern: ??? case (Foo&Bar) fb: > Now, all of this has no bearing on whether or not guards are required > to be ?top level only? in all cases; it argues only that guards need > not appear within pattern-conjunction chains. ?But I believe it would > be perfectly reasonable to write > > case Point(int x when x > 0, int y when y > x): > > R?mi has argued that this would be better written > > case Point(int x, int y) when x > 0 && y > x: > > but I would argue that this choice is, and should be, a matter of > style, and when matching against a record with many fields it might be > more readable to mention each field?s constraint next to its binding > rather than to make the reader compare a list of bindings against a > list of constraints. Agreed, this is a user choice. > Bottom line: there are conceptually three distinct combining forms: > > pattern conjunction > guard conjunction > to a pattern, attach a guard > > and it may be a mistake after all to conflate them by trying to use > the same syntax or symbol for all three. > > So what I would like to see is the convincing application example > where you really do want to write > > pattern & guard & pattern Did the "guard in the middle of the JSON blob" do that? > because then everything I?ve written above falls to the ground. Well, even if so, its possible it can be propped up, just not using one universal support.? Suppose we allow guards to be conjoined on the end of a pattern with &&.? Then you could say ??? case Foo(var x) && x > 0: as well, even, as ??? case Foo(var x && x > 0): But if you wanted to do the P & g & Q thing, you'd need a grobble-style pattern to do so: ??? case P & grobble(g) & Q: Recall that we will eventually be able to write `grobble()` as an ordinary declared pattern, and we can also still resurrect the true/false built-in patterns if we like, for rescuing this case.? (I still think we may eventually want a `non-null(e)` pattern, as it will likely be the most common form of grobbling: ??? case Foo(var x && non-null(x)): > Sorry to bikeshed here, but while ?when? is nice, I think ?if? is even > more appealing (short, familiar, already a keyword), especially if it > alone can express attachment of a guard to a pattern (and we can argue > about whether the parentheses are required): > > case Foo(var x) if (x > 0): There's precedent for this, of course.? My concern with this is that the colon too easily hides the flow of what's going on: ??? case Foo(var x) if (x > 0): if (x > 10) println(x); Having `if` on both sides there seems likely to lead to both "which is which" confusion, as well as "why can't I have Perl-style `if` at the end of a statement." > >> ??? case grobble(e): >> >> which is later revealed to be sugar for: >> >> ??? case Foo(var _) & grobble(e): > > I think you meant it is sugar for > > ? ? case var _ & grobble(e): Yes. > If so, then compare that to the claim that > > ? ? case?if (e): > > is sugar for > > ? ? case?var _ if (e): Or: ??? case &&e: From brian.goetz at oracle.com Fri Mar 5 23:13:25 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 5 Mar 2021 18:13:25 -0500 Subject: Two new draft pattern matching JEPs In-Reply-To: References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> Message-ID: <722016c6-d753-92e0-4ce0-1c35a01a713b@oracle.com> Complete agreement.? This amounts to a hidden laziness requirement that, while it might be possible to support it for built-in patterns, would present hideous translation challenges for nontrivial declared patterns. On 3/5/2021 5:41 PM, Guy Steele wrote: > A compiler would likely optimize common special cases to effectively implement all-at-once population of bindings when it would be impossible to detect any difference in behavior. But I don?t think all-at-once population is the right theoretical model. From john.r.rose at oracle.com Fri Mar 5 23:32:04 2021 From: john.r.rose at oracle.com (John Rose) Date: Fri, 5 Mar 2021 15:32:04 -0800 Subject: Guards In-Reply-To: References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <7F3E367A-141B-4A1E-8837-4C7A27BACD0F@oracle.com> Message-ID: <7D85C055-2C94-4B7C-8266-32810B360C3B@oracle.com> On Mar 5, 2021, at 3:11 PM, Brian Goetz wrote: > > > Or: > > case &&e: Yes, I was reticent about that, but perhaps unary ?&&? is a reasonable marker for ?go into expression land?. It could also help to mark in-args for deconstructor methods. From john.r.rose at oracle.com Fri Mar 5 23:36:53 2021 From: john.r.rose at oracle.com (John Rose) Date: Fri, 5 Mar 2021 15:36:53 -0800 Subject: Two new draft pattern matching JEPs In-Reply-To: References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> Message-ID: <9FB1091C-ED95-476D-91BD-7EA90948B90F@oracle.com> On Mar 5, 2021, at 2:41 PM, Guy Steele wrote: > >> because at the point where the pattern true(...) is evaluated, the Rectangle has already been destructured, obviously, we can ban this kind of patterns to try to conserve the left to right evaluation but the it will still leak in a debugger, you have access to the value of 'y' before the expression inside true() is called. > > I would like to question your assertion > > bindings are all populated at the same time by a deconstructor > > Is this really necessarily true? Same question from me, and I hope it?s not true. And I see Brian has also piled on here! > I would have thought that the job of the deconstructor is to provide the values for the bindings, and that int principle the values are then kept in anonymous variables or locations while the subpatterns are processed, one by one, from left to right. Because consider a more complex pattern: For records that is true. But eventually I would say that the job of the deconstructor is simply to provide some sort of bundle of bits which might need more ?cooking? in order to unveil the components. Then, each occurrence of a binding variable or a sub-pattern triggers that cooking, but a don?t-care pattern (_ or ?) does *not* trigger the cooking. Who cares when something is cooked? Well, if a component of an object is virtualized (NOT the case with records) then it might cost something to reify it. As a simple example, an array component might require that an underlying array be defensively copied. As a more subtle example, a RE-matching pattern might require a lot of additional ?cooking? to reify the contents of an RE expression group. So, my model of a deconstructor, in general, is of one operation which produces a handle of some sort, plus N additional operations which optionally extract the N components. Or, almost equivalently, it is a configurable method which produces up to N values, based on how it is invoked. (There?s a job for indy here.) The body of a user-defined deconstructor could exercise all these degrees of freedom, if there were a way to ?hook up? all of the out-vars, but also provide an ?out-var-requested? flag for each one: class PairOfArrays { int[] a; int[] b; __Deconstructor(out int[] aa, out int[] bb) { if (aa requested) aa = a.clone(); if (bb requested) bb = b.clone(); } } Here, the set of requested variables in the pattern affects how much work the deconstructor does. ? John From guy.steele at oracle.com Fri Mar 5 23:39:47 2021 From: guy.steele at oracle.com (Guy Steele) Date: Fri, 5 Mar 2021 18:39:47 -0500 Subject: Guards In-Reply-To: <7D85C055-2C94-4B7C-8266-32810B360C3B@oracle.com> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <7F3E367A-141B-4A1E-8837-4C7A27BACD0F@oracle.com> <7D85C055-2C94-4B7C-8266-32810B360C3B@oracle.com> Message-ID: <1FA63E13-6C36-4BE4-B139-F811087AAD3D@oracle.com> > On Mar 5, 2021, at 6:32 PM, John Rose wrote: > > On Mar 5, 2021, at 3:11 PM, Brian Goetz wrote: >> >> >> Or: >> >> case &&e: > > Yes, I was reticent about that, but perhaps > unary ?&&? is a reasonable marker for > ?go into expression land?. It could also > help to mark in-args for deconstructor > methods. Ah, you have put your finger on what bothers me about using the specific symbol `&&` in this specific way: it looks like a unary operator, and unary operators bind tight. case && x > 0: looks like it ought to mean case ((&& x) > 0): Now, case var _ && x > 0: clearly does the job, and the abbreviated form case _ && x > 0: I could get behind. But even that is beginning to be a lot of noise to introduce the guard. Maybe we are just trying too hard. Maybe we should use && to introduce guards, but case var _ && x > 0: can be abbreviated as when x > 0: ? From john.r.rose at oracle.com Sat Mar 6 00:09:06 2021 From: john.r.rose at oracle.com (John Rose) Date: Fri, 5 Mar 2021 16:09:06 -0800 Subject: Two new draft pattern matching JEPs In-Reply-To: References: Message-ID: I did careful pass over the docs and here are a couple more comments. You anticipated them at the end with the ?here?s what else we might do? section. The ?as? pattern which binds a value before doing more pattern matching on it should can be synthesized from ?var? and ?&?, as ?P & var x?. The rules for inferring ?var? types should take into account left context as much as possible; I don?t think that?s all there yet. (I think ?var x & P? is a novice?s error, since the inferred type is poor.) To make it easy to use I suggest that ?var x = P? be sugar for ?P & var x?, and the same if ?var? is replaced by a type name. I think you want don?t-care patterns, and ?var _? recommends itself pretty strongly. Perhaps bare ?_? is sugar for ?var _?, or maybe there?s no need. You noted that array patterns sometimes want to refer into the middle of the array. I think that you can do this with the array-index designator syntax you mention, but perhaps it can bye done more economically by allowing the ellipsis ??? to take an optional anchoring index, to its right: { ?[8], eightElement, ninthElement, ? } int[] a & { first, second, ?[a.length-2], penult, last } In some cases (including that last one) the [N] can be inferred, and should be. Thus, the trailing ? your document supports now is revealed to be ?[a.length-N], where N is the (statically determined) length of everything before the ellipsis. I think I like adding an anchor to ??? better than having an ad hoc random access { [n] -> P } syntax, because it makes visual accounting of the whole array, from left to right. But I can see arguments for the random-access form (it doesn?t have to be left-to-right). If and when we do special construction expression syntax for list, map, set, and 99 other user-defined types (I?m serious about the 99; that?s a lower limit) then we will want to make associated pattern syntaxes available. The array stuff is a harbinger of this, since (as is rightly so) the construction syntax new int[]{stuff} is associated with the deconstruction syntax which is exactly the same, except (a) the ?new? is gone, and (b) the ?stuff? is pattern-land instead of expression-land. Regarding the switch-case syntax, I think you have done a good job finely dividing out the syntaxes. You mention constraints that force switches to be either pattern switches or non-pattern switches. (This is near the example with ?error1?.) Maybe this could be expressed by documenting three super-classes of switch cases: pattern, non-pattern, and either. Then the rule boils down to saying that a switch may not have cases which are pattern cases and also cases which are non-pattern cases. At present there are wordy lists, which are hard to keep straight. For a non-pattern switch, you conservatively left out ?case null,1,2,3:?. I think that?s too conservative. I won?t always want to route null to the default case. By adding ?default? as a pseudo-case-label, you made me remember that sometimes I write switches like this: switch (color) { case RED: return ?red?; case BLUE: return ?blue?; case NO_COLOR: //beware fallthrough default: return ?nothing of interest?; } Adding ?case NO_COLOR? here is purely for show, but sometimes you need some extra show, to help the reader reason about special values (such as NO_COLOR). Converting to the rule form to avoid fall through, I want: switch (color) { case RED -> return ?red?; case BLUE -> return ?blue?; case NO_COLOR, default -> return ?nothing of interest?; } Now null is also a special value, so I might also want: switch (color) { case RED -> return ?red?; case BLUE -> return ?blue?; case null, NO_COLOR, default -> return ?nothing of interest?; } (You started it, and I?m rounding up a few more use cases.) > 2. The scope of a pattern variable declaration occurring in a case label of a switch labeled statement group, where there are no further switch labels that follow, includes the block statements of the statement group suggest s/follow/immediately follow/ > On the other hand, falling through a label that does not declare a pattern variable is safe. You might want to point out that in ?test6? that ?s? is no longer in scope after the default label. I didn?t see it clearly explained, though it must be true. Thanks for this great work! ? John From john.r.rose at oracle.com Sat Mar 6 00:11:13 2021 From: john.r.rose at oracle.com (John Rose) Date: Fri, 5 Mar 2021 16:11:13 -0800 Subject: [External] : Re: Two new draft pattern matching JEPs In-Reply-To: <3855bd20-8406-a7a3-ee5f-d25566a32b8e@oracle.com> References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> <871dca64-dd15-b67c-4cde-c837a0371e5c@oracle.com> <1485621441.631660.1614873395892.JavaMail.zimbra@u-pem.fr> <3855bd20-8406-a7a3-ee5f-d25566a32b8e@oracle.com> Message-ID: On Mar 4, 2021, at 8:28 AM, Brian Goetz wrote: > > A lot of people (initially, me included) would like to just interpret a boolean expression as a pattern: > > case Foo(var x, var y) && x > y: ... > > I found that goal compelling, but as patterns get more complicated, this gets unreadable. A main benefit of the true() patterns (or, the explicit guard() pattern declared above) is that it "quarantines" the expression in an expression wrapper; there's a clear boundary between "pattern world" and "expression world?. Yes, this is the stubborn root cause we are working around. From john.r.rose at oracle.com Sat Mar 6 02:14:08 2021 From: john.r.rose at oracle.com (John Rose) Date: Fri, 5 Mar 2021 18:14:08 -0800 Subject: Guards In-Reply-To: <4235D960-D421-45DF-B725-40D7584945CF@oracle.com> References: <4235D960-D421-45DF-B725-40D7584945CF@oracle.com> Message-ID: On Mar 5, 2021, at 3:08 PM, John Rose wrote: > > s/when/&&/ is my proposal; then &&guard is the visual cue, > and you can stack them. (If you want || or ?:, you will need > to use parens.) The grammatical way to say this is that a switch case pattern can be optionally followed by && and then a ConditionalAndExpression whose form can?t be a||b but can be a&&b. An instanceof pattern has no such rule because as an expression it can be naturally followed by &&a which takes the role of a guard in that case. This makes me wonder: What happens when you stick &&a&b after a case? Well, that?s pretty simple; a and b must be boolean expressions. Here?s a better one: What if you write x instanceof T t & q? Of course q is an expression. If you dress up q to resemble a pattern you might have a nice puzzler. So how do I write it if I really want two patterns? Maybe we enlist parentheses? I saved the best for last. What does this do? x instanceof Integer t & t > 0 The answer indicates that users of patterns will have to be on their toes about the difference between & and &&. This makes me feel more confident about using bot & and && for syntax distinctions, as an approximation to overloading simple & for both patterns and guards. && is really a pattern breaker whereas & is a pattern continuer. Answer to puzzler:t can only be flow-scoped after && not &. From brian.goetz at oracle.com Sat Mar 6 17:22:29 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 6 Mar 2021 12:22:29 -0500 Subject: Guards In-Reply-To: <4235D960-D421-45DF-B725-40D7584945CF@oracle.com> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <7F3E367A-141B-4A1E-8837-4C7A27BACD0F@oracle.com> <4235D960-D421-45DF-B725-40D7584945CF@oracle.com> Message-ID: <563abbf1-ed9e-476e-1c3d-58aa56ea4bbc@oracle.com> I think this is the key observation: > And because patterns can nest,*this ?right edge? can occur placed inside a sub-pattern*, which lets us smuggle guards > anywhere we like, with a little care: P&(var x when g(x))&Q. Our diffidence about a "Patterns + Guards" construct is that forcing all the guards to the end seems too constraining; I was never convinced that P & G & Q is freely substitutible in practice for P & Q & G (even if the differences are only "nonfunctional").? But, if we posit, as Guy did: ??? (&&) :: Pattern -> BoolExpr -> Pattern ??? (&) :: Pattern -> Pattern -> Pattern then, using the existing precedence of && and &, then "P and g and Q" can be expressed as (P && g) & Q, because (P&&g) is a pattern, which can be ANDed with pattern Q. Then: ??? if (x instanceof P && g) parses using existing rules as the boolean expression ??? if ( (x instanceof P) && g ) by ordinary operator precedence, and ??? if (x instanceof (P && g)) parses as the match of x to the guarded pattern (P && g), which in this case is silly but harmless, but may be useful when we are &ing more patterns together.? And: ??? case P && g: is an ordinary pattern case with the pattern P-guarded-by-g, no special switch magic. As John says, we don't need & immediately, but there's room for it when we need it with the semantics we expect. What this doesn't give us is a simple form for ??? case in a pattern switch, but there are plenty of ad-hoc ways to do that: ??? case boolean(e): ??? case true(e): ??? case &&e: ??? case if e: ??? case _ && e: From brian.goetz at oracle.com Sat Mar 6 17:51:34 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 6 Mar 2021 12:51:34 -0500 Subject: Two new draft pattern matching JEPs In-Reply-To: References: Message-ID: <0762f635-ca2b-114d-8691-bcb50e77173d@oracle.com> > The ?as? pattern which binds a value before doing > more pattern matching Note that for all the patterns we have right now, there is no need: type patterns, record/deconstruction patterns, and array patterns all take an identifier (optional in the last two) which is the as-slot.? I believe users will find: ??? case Point(var x, var y) p: much more natural than ??? case Point(var x, var y) & var p: and, additionally, if we intend to generalize local declaration / method parameters to support total patterns, then we will want this extra syntax anyway: ??? void m(Point(var x, var y) p)? // p is surely required here So the "as" slot right now is a theoretical concern, but, your point is taken, that a conjoined `var` pattern can act as an "as". > on it should can be synthesized > from ?var? and ?&?, as ?P & var x?. The rules for > inferring ?var? types should take into account > left context as much as possible; I don?t think > that?s all there yet. (I think ?var x & P? is a > novice?s error, since the inferred type is poor.) I think what you're saying is that if P has a target-type of T, and Q has a target-type of U, then in ??? x instanceof (P & Q & var x) and, given x : X, then, since we won't even try to match `var x` unless P and Q already match, we seed inference with constraints x <: X, x <: T, and X <: U, and will generally get out (some normalization of) x <: T&U&X. > You noted that array patterns sometimes want to > refer into the middle of the array. I think this is relatively low on the priority list; matching the whole array, or an initial segment of the array, seem natural use cases for the language, but I'm not sure I want to embed a comprehension-query language to pick up the rest of the use cases. > If and when we do special construction expression syntax > for list, map, set, and 99 other user-defined types (I?m > serious about the 99; that?s a lower limit) then we will > want to make associated pattern syntaxes available. > The array stuff is a harbinger of this, since (as is rightly > so) the construction syntax new int[]{stuff} is associated > with the deconstruction syntax which is exactly the > same, except (a) the ?new? is gone, and (b) the ?stuff? > is pattern-land instead of expression-land. The array syntax is closely related to declared patterns with varargs (of which varargs records are already on the docket.) > You mention constraints that force switches to be > either pattern switches or non-pattern switches. > (This is near the example with ?error1?.) This was intended as a transitional ("Preview 1") simplification.? A constant case `case L` can be desugared to `case var x && x == L` relatively easy. > For a non-pattern switch, you conservatively > left out ?case null,1,2,3:?. I think that?s too > conservative. I won?t always want to route > null to the default case. Yep, good catch. From brian.goetz at oracle.com Sat Mar 6 17:52:56 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 6 Mar 2021 12:52:56 -0500 Subject: [External] : Re: Two new draft pattern matching JEPs In-Reply-To: References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> <871dca64-dd15-b67c-4cde-c837a0371e5c@oracle.com> <1485621441.631660.1614873395892.JavaMail.zimbra@u-pem.fr> <3855bd20-8406-a7a3-ee5f-d25566a32b8e@oracle.com> Message-ID: <2646fb67-5050-7c0c-650c-a21fba5ded26@oracle.com> On reflection, I think this is just something we have to trust users to do responsibly; use parentheses, newlines, and indentation to make their code clear. On 3/5/2021 7:11 PM, John Rose wrote: > On Mar 4, 2021, at 8:28 AM, Brian Goetz > wrote: >> >> A lot of people (initially, me included) would like to just interpret >> a boolean expression as a pattern: >> >> ??? case Foo(var x, var y) && x > y: ... >> >> I found that goal compelling, but as patterns get more complicated, >> this gets unreadable.? A main benefit of the true() patterns (or, the >> explicit guard() pattern declared above) is that it "quarantines" the >> expression in an expression wrapper; there's a clear boundary between >> "pattern world" and "expression world?. > > Yes, this is the stubborn root cause we are working around. > > > From sirinath1978m at gmail.com Sun Mar 7 02:24:47 2021 From: sirinath1978m at gmail.com (Suminda Sirinath Salpitikorala Dharmasena) Date: Sun, 7 Mar 2021 07:54:47 +0530 Subject: Guards In-Reply-To: <563abbf1-ed9e-476e-1c3d-58aa56ea4bbc@oracle.com> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <7F3E367A-141B-4A1E-8837-4C7A27BACD0F@oracle.com> <4235D960-D421-45DF-B725-40D7584945CF@oracle.com> <563abbf1-ed9e-476e-1c3d-58aa56ea4bbc@oracle.com> Message-ID: Many be use of & and && for this purpose may be confusing. Perhaps something like this would be better: case pattern, pattern, pattern {if(predicate)}, pattern {if(predicate)}, {if(predicate)}, {if(predicate)}, pattern, pattern We can still have both case Point(int x {if (x > 0)}, int y {if (y > x)}): and case Point(int x, int y) {if (x > 0 && y > x)}: or simply: case Point(int x {if (x > 0)}, int y {y > x}): and case Point(int x, int y) {x > 0 && y > x}: Use of {} would make it easy to see that this is a predicate. From sirinath1978m at gmail.com Sun Mar 7 02:28:03 2021 From: sirinath1978m at gmail.com (Suminda Sirinath Salpitikorala Dharmasena) Date: Sun, 7 Mar 2021 07:58:03 +0530 Subject: Guards In-Reply-To: References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <7F3E367A-141B-4A1E-8837-4C7A27BACD0F@oracle.com> <4235D960-D421-45DF-B725-40D7584945CF@oracle.com> <563abbf1-ed9e-476e-1c3d-58aa56ea4bbc@oracle.com> Message-ID: A typo: or simply: case Point(int x {if (x > 0)}, int y {y > x}): should read as: or simply: case Point(int x {x > 0}, int y {y > x}): From maurizio.cimadamore at oracle.com Mon Mar 8 15:54:22 2021 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 8 Mar 2021 15:54:22 +0000 Subject: Guards In-Reply-To: References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <7F3E367A-141B-4A1E-8837-4C7A27BACD0F@oracle.com> Message-ID: <860b0b56-9563-1aee-b1fc-990165350696@oracle.com> Hi Brian On 05/03/2021 23:11, Brian Goetz wrote: > I have long had a nagging feeling that this will eventually be > desirable.? Let's say we have P & g & Q & h; under what conditions can > we commute g and Q without regret?? I can think of four potential > sources of regret: > > ?- g declares bindings that are inputs to Q > ?- the cost model of Q is such that we'd like to run g first, and > short-circuit > ?- Q might throw an exception when g does not hold > ?- Q might have side-effects that we don't want to run if g does not hold You are right to point out that swapping guards with patterns is not free - that said, if a guard doesn't introduce any additional bindings, then the first point also is not a concern. Why am I focusing on guards that do not introduce new bindings? Simply because, IMHO, a guard after a pattern is meant to add extra "imperative" conditions on one or more of the bindings that have already been extracted. Sure, you can (in principle) also use it to declare additional binding which you can use elsewhere - but I'm less sure that this is a use of a guard I would agree with. It seems to me that if you want to use the guard to add extra bindings, you can get there by nesting a pattern - e.g. Point(var x, var y) when pred(x) && pred(y) vs Point(pred(var x), pred(var y)) Maurizio From forax at univ-mlv.fr Tue Mar 9 18:02:38 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 9 Mar 2021 19:02:38 +0100 (CET) Subject: [External] : Re: Two new draft pattern matching JEPs In-Reply-To: <3855bd20-8406-a7a3-ee5f-d25566a32b8e@oracle.com> References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> <871dca64-dd15-b67c-4cde-c837a0371e5c@oracle.com> <1485621441.631660.1614873395892.JavaMail.zimbra@u-pem.fr> <3855bd20-8406-a7a3-ee5f-d25566a32b8e@oracle.com> Message-ID: <691215171.1337502.1615312958342.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "Gavin Bierman" , "amber-spec-experts" > Envoy?: Jeudi 4 Mars 2021 17:28:05 > Objet: Re: [External] : Re: Two new draft pattern matching JEPs >> I want to separate the discussions about & between patterns and true()/false() >> aka mix pattern and expressions, >> because the later is a call for trouble for me, the former is just a question >> about adding a new pattern or not. >> >> I see true() and false() has an heresy because conceptually a bunch of patterns >> is a kind of declarative API while expressions are not. > > If it helps, let me trace the story of where the true/false pattern idea > came from.? We have had "method patterns" (need a better name) in the > model forever; we have anticipated that some method patterns (e.g., map > get, regex match) will distinguish between input and output parameters. > And we have imagined AND and OR combinators for a long time as well. > > One day, it occurred to me that, with the ultimate pattern model we are > envisioning, we don't need a linguistic notion of guards at all!? I > could write an ordinary pattern: > > ??? static pattern guard(boolean b) { > ??????? if (!b) __FAIL; > ??? } > > and voila, we can express guards as: > > ??? case P & guard(e): > > This was a powerful realization, because it meant that the notion of > "guard" was "not a primitive"; it was something we could compose from > existing envisioned tools.? Except, we don't have method patterns yet > (and are not ready to do them), so ... why not treat the existing > reserved identifier `true` as if it were declared like `guard` above? > > (What's weird about this pattern is that it ignores its target. That's > "clever", and clever can be confusing.) This open a lot of questions in my mind. As you said there is no target, but also it seems that the argument of the boolean parameter can also access to the previous bindings, something which is far from obvious to me. Until now the bindings were only available after ':' or '->' for a case, i.e after the pattern part, not in between patterns. That seems a huge leap to me. You said in an earlier email that apart the target, the parameters of a pattern should be a kind of constant, you mention something along the side of effectively final, here you go full steam in the opposite direction saying a pattern can take as parameter an expression created from the bindings available at that point. If we go in that direction, i'm not sure to understand what is a pattern anymore, what is the difference between a pattern and a method in that case, why we should we have pattern methods in a class because it seems that a there are taking expression as parameter as the classical methods. In that case, perhaps every "pattern" should take an expression, and the expression on which the switch is done should have a default binding like 'it' so we can write something like switch(o) { case (var a, var b) = Foo.destruct(it) -> } or using something like the walrus operator switch(o) { case Foo.destruct(it) := var a, var b -> } if we want to keep the bindings on the right side, after the pattern itself. So we have a consistent view, a pattern can takes bindings, 'it' is the default binding, there is no need to a pattern method because this is just syntactic sugar on top of a classical method call. > > I don't say this to justify the syntax; I get that pressing `true` into > service may feel a bit of a stretch.? My point here is that the > true/false patterns proposed in the JEP are *not special*; the are > something we could declare eventually with the model that we expect to > get to.? (They're like record patterns, in a sense; eventually all > classes will be able to declare deconstruction patterns, but records are > special in that they get one even without declaring it.)? So if you find > them heretical, then you should also have a problem with expressing > map-get or regex match as a pattern, no? And if so, let's put the > complaint where it belongs; the use of expressions in true/false is just > a symptom of your distress, not the cause.? Let's talk about the cause. > >> Allowing expression right inside a pattern means that you can witness the >> evaluation order of patterns, something we don't want. > > Note that this is unavoidable when we get to declared patterns; if there > are side effects, you can witness their order and arity.? But, this is > also not your real point.? So, let's get to the real point, so we can > discuss that, because whether we leak an unspecified order of evaluation > through side-effects is mostly a distraction. > >> There is a misunderstanding here, i'm referring to the fact that >> case Point(1, 1): >> is actually rejected because it's too much like new Point(1,1) but at the same >> time, you want to allow expressions in the middle of patterns. > > Actually, the opposite! > > A lot of people (initially, me included) would like to just interpret a > boolean expression as a pattern: > > ??? case Foo(var x, var y) && x > y: ... > > I found that goal compelling, but as patterns get more complicated, this > gets unreadable.? A main benefit of the true() patterns (or, the > explicit guard() pattern declared above) is that it "quarantines" the > expression in an expression wrapper; there's a clear boundary between > "pattern world" and "expression world". > > In any case, you are distorting the claim of "no expressions in the > middle of patterns."? We have always envisioned specific contexts where > expressions can be dropped into patterns (map get, regex), but we have > worked to ensure there is a *clear syntactic division*, so that it is > clear from the syntactic structure whether something is a pattern or an > expression. > see above, being able to access to the bindings as argument of a pattern is something new to me and kind a in between two states, it's not fully a pattern where the matching and the binding are not related (apart the target) and it's not fully an expression too. >> For (a), yes it's switch specific and it's great because we don't need it for >> instanceof, you can already use && inside the if of an instanceof and you don't >> need it when declaring local variables because the pattern has to be total. So >> being specific to switch is not really an issue. > > Only true if `instanceof` and `switch` are the only contexts where you > can imagine patterns. What about, say, `catch`?? If you nail a bag on > the side of switch, you will have to nail the same bag on the side of > catch.? Which might be acceptable, and the same bag might fit, but > that's yet more ad-hoc language surface. > >> For (b), you can always shift all the contents of true() and false() to the >> right into a traditional guard, so we don't need true() and false() > > In theory true, but only in a world where pattern evaluation is > cost-free, exception-free, and side-effect free.? (That's why && is > short circuiting -- because these things are not always true.) > > I'm not deaf to the argument that "nailing a bag on the side will be > easier for developers to accept", but please, let's stop pretending it's > not a bag.? If you want to advocate for "bag", then please, make that > case directly! It's advocating to have three separates zones that all have a well defined semantics, the first part (the pattern part) is a pattern wich can have sub-pattern a provides bindings, the second optional part (the guard part) is an expression that can access to the binding to refine the pattern, the last part is either en expression of a block of code as in a classical switch. And yes, you are losing the short-circuit ability, because the guard expression is pushed on the right side, but you keep a clean separation of the different zones. R?mi From forax at univ-mlv.fr Tue Mar 9 18:17:55 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 9 Mar 2021 19:17:55 +0100 (CET) Subject: Two new draft pattern matching JEPs In-Reply-To: References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> Message-ID: <2128183471.1343559.1615313875965.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Guy Steele" > ?: "Remi Forax" > Cc: "Gavin Bierman" , "amber-spec-experts" > Envoy?: Vendredi 5 Mars 2021 23:41:30 > Objet: Re: Two new draft pattern matching JEPs >> On Mar 3, 2021, at 7:50 AM, Remi Forax wrote: >> >> ----- Mail original ----- >>> De: "Gavin Bierman" >>> ?: "amber-spec-experts" >>> Envoy?: Jeudi 18 F?vrier 2021 13:33:20 >>> Objet: Two new draft pattern matching JEPs >> >>> Dear all, >>> >> >> [...] >> >>> >>> - Pattern Matching for switch: https://bugs.openjdk.java.net/browse/JDK-8213076 >>> >>> We split them up to try to keep the complexity down, but we might decide to >>> merge them into a single JEP. Let me know what you think. >> >> I think that we have got a little over our head with the idea of replacing the >> switch guard by the guard pattern + conditional-and pattern. >> >> The draft is poor in explanations on why we should do that apart because it's >> more powerful, which is true but that not how to evaluate a feature. >> Here, it doesn't seem we are trying to fix a broken feature or adapt an existing >> feature to Java. It's just more powerful, but with a lot of drawbacks, see >> below. >> >> My main concern is when mixing the deconstructing pattern with the guard + and >> pattern, those twos (two and a half) doesn't mix well. >> >> For a starter, at high level, the idea is to mix patterns and expressions >> (guards are boolean expressions), but at the same time, we have discussed >> several times to not allow constants inside patterns to make a clear >> distinction between patterns and expressions. We have a inconsistency here. >> >> The traditional approach for guards cleanly separate the pattern part from the >> expression part >> case Rectangle(Point x, Point y) if x > 0 && y > 0 >> which makes far more sense IMO. >> >> The current proposal allows >> case Rectangle(Point x & true(x > 0), Point y & true(y > 0)) >> which is IMO far least readable because the clean separation between the >> patterns and the expressions is missing. > > As I have already indicated in another email, if Rectangle had six or eight > components rather than just two, for some purposes it might be more readable to > have the constraint for each component listed next to its binding, rather than > making the reader compare a long list of bindings to a long list of > constraints. > > [more below] > >> There is also a mismatch in term of evaluation, an expression is evaluated from >> left to right, for a pattern, you have bindings and bindings are all populated >> at the same time by a deconstructor, this may cause issue, by example, this is >> legal in term of execution >> case Rectangle(Point x & true(x > 0 && y > 0), Point y) >> because at the point where the pattern true(...) is evaluated, the Rectangle has >> already been destructured, obviously, we can ban this kind of patterns to try >> to conserve the left to right evaluation but the it will still leak in a >> debugger, you have access to the value of 'y' before the expression inside >> true() is called. > > I would like to question your assertion > > bindings are all populated at the same time by a deconstructor > > Is this really necessarily true? I would have thought that the job of the > deconstructor is to provide the values for the bindings, and that int principle > the values are then kept in anonymous variables or locations while the > subpatterns are processed, one by one, from left to right. Because consider a > more complex pattern: > > case Rectangle(Point(int x1, int y1), Point(int x2, int y2)) > > I would expect that the deconstructor for Rectangle does not fill in all four > variables x1, y1, x2, y2 all at once; rather, it just supplies two values that > are points, and then the first point value is matched against pattern Point(int > x1, int y1), and only then is the second point value matched against pattern > Point(int x2, int y2)). yes, that why i said "by a deconstructor", maybe i should have write, for ONE deconstructor. The deconstructor of Rectangle provides two bindings which both used as target parameter of the deconstructor of Point (which is called twice). > > Now this example is not exactly analogous to your original, because we have not > provided explicit variables for this purpose. I believe that in an earlier > version of the design one would write > > case Rectangle(Point(int x1, int y1), Point(int x2, int y2)) > > But perhaps in the current proposal one must write > > case Rectangle(Point(int x1, int y1) & var p1, Point(int x2, int y2) & var p2) > > or perhaps > > case Rectangle(var p1 & Point(int x1, int y1), var p2 & Point(int x2, int y2)) > > In all of these cases, my argument is still the same: the simplest model is that > that deconstructor for Rectangle just supplies two values that are points, and > then the first point value is matched against the first sub pattern, and only > then is the second point value matched against the second subpattern. As a > result p2 and x2 and y2 do not yet have bindings or values while the first > sub-pattern is being matched. I think we agree here, i was just saying that for a deconstructor call you get all the bindings at the same time, so in the case there is two bindings, having two expressions that each one guard one binding is equivalent to have one guard that uses the two bindings. Obviously, when patterns are nested, you don't have access to all the bindings of all the patterns at once. Now, i don't think you have to use an & between patterns to provide a name, in my opinion, we should things in the other way case Rectangle(Point(int x1, int y1), Point(int x2, int y2)) should be a simplified way to write case Rectangle(Point(int x1, int y1) _, Point(int x2, int y2) _) i.e. when destructuring, we don't provide a name for that binding (hence my use of '_') With that in mind, if you want to name the intermediary point, you can just write case Rectangle(Point(int x1, int y1) p1, Point(int x2, int y2) p2) > > A compiler would likely optimize common special cases to effectively implement > all-at-once population of bindings when it would be impossible to detect any > difference in behavior. But I don?t think all-at-once population is the right > theoretical model. > > [more below] > >> In term of syntax, currently the parenthesis '(' and ')' are used to >> define/destructure the inside, either by a deconstructor or by a named pattern, >> but in both cases the idea is that it's describe the inside. Here, true() and >> false() doesn't follow that idea, there are escape mode to switch from the >> pattern world into the expression world. >> At least we can use different characters for that. >> >> Also in term of syntax again, introducing '&' in between patterns overloads the >> operator '&' with one another meaning, my students already have troubles to >> make the distinction between & and && in expressions. >> As i already said earlier, and this is also said in the Python design document, >> we don't really need an explicit 'and' operator in between patterns because >> there is already an implicit 'and' between the sub-patterns of a deconstructing >> pattern. > > As I have already indicated in another email, I agree with you here; I very much > share your concerns about the overloading of a single symbol `&` (or whatever > spelling we give it) to mean two or three different things within patterns, not > to mention its existing uses in expression contexts. > R?mi From forax at univ-mlv.fr Tue Mar 9 18:21:47 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 9 Mar 2021 19:21:47 +0100 (CET) Subject: Two new draft pattern matching JEPs In-Reply-To: <9FB1091C-ED95-476D-91BD-7EA90948B90F@oracle.com> References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> <9FB1091C-ED95-476D-91BD-7EA90948B90F@oracle.com> Message-ID: <822217422.1345070.1615314107014.JavaMail.zimbra@u-pem.fr> > De: "John Rose" > ?: "Guy Steele" > Cc: "Remi Forax" , "Gavin Bierman" > , "amber-spec-experts" > > Envoy?: Samedi 6 Mars 2021 00:36:53 > Objet: Re: Two new draft pattern matching JEPs > On Mar 5, 2021, at 2:41 PM, Guy Steele < [ mailto:guy.steele at oracle.com | > guy.steele at oracle.com ] > wrote: >>> because at the point where the pattern true(...) is evaluated, the Rectangle has >>> already been destructured, obviously, we can ban this kind of patterns to try >>> to conserve the left to right evaluation but the it will still leak in a >>> debugger, you have access to the value of 'y' before the expression inside >>> true() is called. >> I would like to question your assertion >> bindings are all populated at the same time by a deconstructor >> Is this really necessarily true? > Same question from me, and I hope it?s not true. > And I see Brian has also piled on here! >> I would have thought that the job of the deconstructor is to provide the values >> for the bindings, and that int principle the values are then kept in anonymous >> variables or locations while the subpatterns are processed, one by one, from >> left to right. Because consider a more complex pattern: > For records that is true. But eventually I would say that the > job of the deconstructor is simply to provide some sort of > bundle of bits which might need more ?cooking? in order > to unveil the components. Then, each occurrence of a > binding variable or a sub-pattern triggers that cooking, > but a don?t-care pattern (_ or ?) does *not* trigger the > cooking. Who cares when something is cooked? Well, > if a component of an object is virtualized (NOT the case > with records) then it might cost something to reify it. > As a simple example, an array component might require > that an underlying array be defensively copied. > As a more subtle example, a RE-matching pattern > might require a lot of additional ?cooking? to > reify the contents of an RE expression group. > So, my model of a deconstructor, in general, is > of one operation which produces a handle of > some sort, plus N additional operations which > optionally extract the N components. Or, almost > equivalently, it is a configurable method > which produces up to N values, based on how > it is invoked. (There?s a job for indy here.) > The body of a user-defined deconstructor could > exercise all these degrees of freedom, if there > were a way to ?hook up? all of the out-vars, > but also provide an ?out-var-requested? flag > for each one: > class PairOfArrays { > int[] a; int[] b; > __Deconstructor(out int[] aa, out int[] bb) { > if (aa requested) aa = a.clone(); > if (bb requested) bb = b.clone(); > } } > Here, the set of requested variables in the pattern > affects how much work the deconstructor does. It seems to be an early over-optimization, i really hope that yoiu can keep the deconstructor protocol far simpler and for your example piggyback on frozen arrays instead. > ? John R?mi From forax at univ-mlv.fr Tue Mar 9 18:32:08 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 9 Mar 2021 19:32:08 +0100 (CET) Subject: Guards In-Reply-To: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> Message-ID: <2136938599.1349657.1615314728393.JavaMail.zimbra@u-pem.fr> Apart from what have said about letting grobble to fully access to the bindings, i think that using parenthesis to call grobble here is already too much. case Foo(var x) when x > y is far more readable than case Foo(var x) when (x > y) because for a pattern the parenthesis describes a notion of ownership, you are describing the content of the type using a destructuring pattern, while the second pair of parenthesis is a kind of method call. Having two pairs of parenthesis with two complete different meanings is something is hope you can avoid, at least for the simple cases. R?mi > De: "Brian Goetz" > ?: "amber-spec-experts" > Envoy?: Vendredi 5 Mars 2021 20:14:01 > Objet: Guards > Let me try and summarize all that has been said on the Guards topic. > #### Background and requirements > For `instanceof`, we don't need any sort of guard right now (with the patterns > we have); we can already conjoin arbitrary boolean expressions with `&&` in all > the contexts we can use `instanceof`, because it's a boolean expression. (This > may change in the future as patterns get richer.) So we can already express our > canonical guarded Point example with > if (p instanceof Point(var x, var y) && x > y) { ... } > with code that no one will find confusing. > For switch, we can't do this, because case labels are not boolean expressions, > they're some ad-hoc sub-language. When the sub-language was so limited that it > could only express int and string constants, this wasn't a problem; there was > little refinement needed on `case "Foo"`. > As we make switch more powerful, we face a problem: if the user drifts out of > the territory of what can be expressed as case labels, they fall off the cliff > and have to refactor their 50-way switch into an if-else chain. This will be a > really bad user experience. Some sort of escape hatch to boolean logic buys us > insurance against this bad experience -- as long as you can express your > non-pattern criteria with a boolean expression (which is pretty rich), you > don't have to leave switch-land. > So we took as our requirement: > Some sort of guard construct that is usable in switch is a forced move. > #### Expressing guards in switch > There are several ways to envision guards: > - As patterns that refine other patterns (e.g., a "true" pattern) > - As an additional feature of "case" in switch (e.g., a "when" clause) > - As an imperative control-flow statement usable in "switch" (e.g., "continue") > We've largely rejected the third (even though it is more primitive than the > others), because we think the resulting code will be much harder to read and > more error-prone. We've bounced back and forth between "let's nail something on > the side of switch" and "let's let the rising pattern tide lift all conditional > constructs." > Other languages have demonstrated that guards in switch-like constructs are > viable. > The argument in favor of nailing something on the side of switch is that it is > pragmatic; it is immediately understandable, it raises the expressivity of > `switch` to where `if` already is, and it solves the immediate requirement we > have in adding patterns to switch. > The argument against is that it is not a primitive; it is dominated by the > option of making patterns richer (by adding boolean patterns), it is weak and > non-compositional, and overly specific to switch. (It is possible to make > worse-is-better arguments here that we should do this anyway, but it's not > really possible to seriously claim better, despite attempts to the contrary.) > #### Interpreting the feedback > The JEP proposes a powerful and compositional approach: > - true/false patterns that accept arbitrary boolean expressions (and which > ignore their target); > - combining patterns with a pattern-AND combinator > On the one hand, this is a principled, orthogonal, compositional, expressive, > broadly applicable approach, based on sensible primitives, which will be usable > in other contexts, and which anticipate future requirements and directions. > On the other hand, there has been a pretty powerful emotional reaction, which > could be summarized as "sorry, we're not ready for this degree of generality > yet with respect to patterns." This emotional reaction seems to have two > primary components: > - A "who moved my cheese" reaction to the overloading of `true` in this way -- > that `true` seems to be, in everyone's mind, a constant, and seeing it as a > pattern is at least temporarily jarring. (This may be a temporary reaction, but > there's still a cost of burning through it.) > - A reaction to "borrowing & from the future" -- because the other use cases for > &-composition are not obvious or comfortable yet, the use of &-composition > seems foreign and forced, and accordingly engenders a strong reaction. > The former (which I think is felt more acutely) could be addressed by taking a > conditional keyword such as `when` here; ad-hoc "focus" research suggests the > negative reaction here is lower, but still there. > The latter is, I think, the more linguistically significant of the two; even > though there is a strong motivation for & coming down the pike, this is not the > gentle introduction to pattern combination that we'd like, and developer's > mental models of patterns may not be ready. Patterns are still new, and we'd > like for the initial experience to make people want more, rather than scare > them with too much up front. > #### Options > I suspect that we'd get a lot of mileage out of just renaming true to something > like "when"; it avoids the "but that's not what true is" reaction, and is > readable enough: > case Foo(var x) & when(x > 0): > but I think it will still be perceived as "glass half empty", with lots of "why > do I need the &" reactions. And, in the trivial (but likely quite common, at > least initially) case of one pattern and one guard, the answers are not likely > to be very satisfying, no matter how solidly grounded in reality, because the > generality of the compositional approach is not yet obvious enough to those > seeing patterns for the first time. > I am not compelled by the direction of "just add guards to switch and be done > with it", because that's a job we're going to have to re-do later. But I think > there's a small tweak which may help a lot: do that job now, with only a small > shadow of lasting damage: > - Expose `grobble(expr)` clauses as an option on pattern switch cases; > - When we introduce & combination (which can be deferred if we have a switch > guard now), plan for a `grobble(e)` pattern. At that point, > case Foo(var x) grobble(x > 0): > is revealed to be sugar for > case Foo(var x) & grobble(x > 0): > As as bonus, we can use grobble by itself in pattern switches to incorporate > non-target criteria: > case grobble(e): > which is later revealed to be sugar for: > case Foo(var _) & grobble(e): > The downside here is that in the long run, we have something like the C-style > array declarations; in the trivial case of a single pattern with a guard, you > can leave in the & or leave it out, not unlike declaring `int[] x` vs `int > x[]`. Like the "transitional" (but in fact permanent) sop of C-style > declarations, the "optional &" will surely become an impediment ("why can I > leave it out here, but not there, that's inconsistent"). > All that said, this is probably an acceptable worse-is-better direction, where > in the short term users are not forced to confront a model that they don't yet > understand (or borrow concepts from the future), with a path to > sort-of-almost-unification in the future that is probably acceptable. From brian.goetz at oracle.com Tue Mar 9 18:47:05 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 9 Mar 2021 13:47:05 -0500 Subject: [External] : Re: Guards In-Reply-To: <2136938599.1349657.1615314728393.JavaMail.zimbra@u-pem.fr> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <2136938599.1349657.1615314728393.JavaMail.zimbra@u-pem.fr> Message-ID: > Apart from what have said about letting grobble to fully access to the > bindings Except that argument doesn't make sense.? Accessing the bindings is not a special behavior of grobble, but a natural consequence of flow scoping.? If I have P && g (or P & grobble(g)), then the scoping rules will tell us that the true set of P is present in g, and we're done.? Nothing special here. From john.r.rose at oracle.com Tue Mar 9 18:56:42 2021 From: john.r.rose at oracle.com (John Rose) Date: Tue, 9 Mar 2021 10:56:42 -0800 Subject: [External] : Re: Guards In-Reply-To: References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <2136938599.1349657.1615314728393.JavaMail.zimbra@u-pem.fr> Message-ID: <9D092917-D5FD-46D4-9988-44A67B4A66B2@oracle.com> On Mar 9, 2021, at 10:47 AM, Brian Goetz wrote: > > >> Apart from what have said about letting grobble to fully access to the bindings > > Except that argument doesn't make sense. Accessing the bindings is not a special behavior of grobble, but a natural consequence of flow scoping. If I have P && g (or P & grobble(g)), then the scoping rules will tell us that the true set of P is present in g, and we're done. Nothing special here. Piling on: If patterns ever have in-args (in addition to out-args, and they will!), then flow-scoping, regularly applied, will allow those in-args to access previously bound out-args to the left, within the same compound pattern, whether in an instanceof pattern or a case label pattern. And, guards (whether built-in && as I claim they should be) or a privileged use of in-args on a standard method, will naturally have access to those same leftward out-arg bindings. I think it?s a great model. The reason we are agonizing here is kind of artificial, because in-args in patterns are a 99.99% probable future, of which guards are the first manifestation. ? John From john.r.rose at oracle.com Tue Mar 9 20:23:33 2021 From: john.r.rose at oracle.com (John Rose) Date: Tue, 9 Mar 2021 12:23:33 -0800 Subject: [External] : Re: Fwd: Two new draft pattern matching JEPs In-Reply-To: <013b18b6-1daf-6e34-5b6b-36fc80d9e090@oracle.com> References: <4e038546-59d6-d6d7-e4cc-5c32a990b656@oracle.com> <0a7c77fa-a5cb-df2f-543e-c0ae297ac6da@oracle.com> <013b18b6-1daf-6e34-5b6b-36fc80d9e090@oracle.com> Message-ID: <437AFE0B-D7C0-42AF-9D17-F364920C9FD4@oracle.com> On Mar 5, 2021, at 8:29 AM, Brian Goetz wrote: > >> >> The one objection I still have to grobble is one I've raised before: it makes it hard to imagine ever representing disjunctions as guards. I always bring up stuff like >> >> switch (people) { >> case Pair(Person(String name1, int age1), Person(String name2, int age2)) >> | age1 > age2 -> name1 + " is older" >> | age2 > age1 -> name2 + " is older" >> | otherwise -> format("%s and %s are the same age", name1, name2) >> } >> > > If we can assume that such "sub-switches" are routinely total on the top pattern, I think we can do this with the tools on the table, perhaps with some small adjustments. I?m going to blow Brian?s cover here, but then explain why it?s a good cover. When I read Alan?s note I thought, ?he wants continue-switch for partial sub-cases?. We should note that this on the table, but on a side-table, sidelined with many other low-priority high-cost features. (A ?sub-case? is some form of case which refines a preceding case. Haskell allows this in the form of |-guards. It?s a nice idea, sometimes. We could bike-shed it in many ways.) By ?continue-switch? I mean a variant of ?continue? which breaks out of the current case and proceeds to the next applicable case, if any, exactly and precisely as ?continue? in a loop breaks out of the current iteration and proceeds to the next iteration, if any. I would paint it ?continue switch? which then begs the question of whether this is a generative construct (as the linguists might say) calling for ?continue K? for K in switch, for, while, do, and (yes!) if. If switch refactors to an if-chain, why then continue-switch would refactor to continue-if, which turns out to branch to the next ?else?, if any. All that is to say, nobody should be surprised that we are keeping this on the low-priority list, because its specification cost would seem to be higher than its occasional utility. ? John From john.r.rose at oracle.com Tue Mar 9 20:37:48 2021 From: john.r.rose at oracle.com (John Rose) Date: Tue, 9 Mar 2021 12:37:48 -0800 Subject: [External] : Re: Fwd: Two new draft pattern matching JEPs In-Reply-To: <437AFE0B-D7C0-42AF-9D17-F364920C9FD4@oracle.com> References: <4e038546-59d6-d6d7-e4cc-5c32a990b656@oracle.com> <0a7c77fa-a5cb-df2f-543e-c0ae297ac6da@oracle.com> <013b18b6-1daf-6e34-5b6b-36fc80d9e090@oracle.com> <437AFE0B-D7C0-42AF-9D17-F364920C9FD4@oracle.com> Message-ID: <4AB3174C-9C11-44C4-9D6B-37B244477E5F@oracle.com> On Mar 9, 2021, at 12:23 PM, John Rose wrote: > > If switch refactors > to an if-chain, why then continue-switch would refactor to > continue-if, which turns out to branch to the next ?else?, > if any. A bit more detail in case that was cryptic: switch (obj) { ... case P &&guard G: ? ? } <=refactor=> switch (obj) { ... case P: { if (!G) continue switch; } ... ? } <=refactor=> val $it = obj; if ... else if ($it instanceof P && G) { ... } else ? <=refactor=> val $it = obj; if ... else if ($it instanceof P) { { if (!G) continue if; } ... } else ? (No sub-cases here, BTW.) The enhanced continue statement allows guards to be refactored to interact with ad hoc variable bindings and/or control flow as: switch (obj) { ... case P: if (Q) return quickwin(); var X = ?; if (!G(X)) continue switch; ... ? } Still, the above does not yet seem to constitute any killer use cases. Also, frankly, ?continue if? is probably liable to bad abuses. From forax at univ-mlv.fr Tue Mar 9 21:45:36 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 9 Mar 2021 22:45:36 +0100 (CET) Subject: [External] : Re: Guards In-Reply-To: <9D092917-D5FD-46D4-9988-44A67B4A66B2@oracle.com> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <2136938599.1349657.1615314728393.JavaMail.zimbra@u-pem.fr> <9D092917-D5FD-46D4-9988-44A67B4A66B2@oracle.com> Message-ID: <1925778026.1382732.1615326336812.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "John Rose" > ?: "Brian Goetz" > Cc: "Remi Forax" , "amber-spec-experts" > Envoy?: Mardi 9 Mars 2021 19:56:42 > Objet: Re: [External] : Re: Guards > On Mar 9, 2021, at 10:47 AM, Brian Goetz wrote: >> >> >>> Apart from what have said about letting grobble to fully access to the bindings >> >> Except that argument doesn't make sense. Accessing the bindings is not a >> special behavior of grobble, but a natural consequence of flow scoping. If I >> have P && g (or P & grobble(g)), then the scoping rules will tell us that the >> true set of P is present in g, and we're done. Nothing special here. > > Piling on: > > If patterns ever have in-args (in addition to out-args, > and they will!), then flow-scoping, regularly applied, > will allow those in-args to access previously bound > out-args to the left, within the same compound pattern, > whether in an instanceof pattern or a case label pattern. I agree that patterns have input args. There are two kinds of input args, one is the implicit target, the others are other arguments. It's not obvious to me that the parameters other than the target have to have access to the bindings. I see the pattern + parameters has a partial application, i.e. the parameters other than the target are constants, the keys of the map are constant, the java.util.regex.Pattern (or the string) of a metch is a constant, etc So yes, a pattern have input parameters other than the target but should they have access to the bindings, i think we are loosing a lot with that model. > > And, guards (whether built-in && as I claim they > should be) or a privileged use of in-args on a standard > method, will naturally have access to those same > leftward out-arg bindings. > > I think it?s a great model. The reason we are agonizing > here is kind of artificial, because in-args in patterns > are a 99.99% probable future, of which guards are > the first manifestation. > > ? John R?mi From brian.goetz at oracle.com Tue Mar 9 21:51:59 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 9 Mar 2021 16:51:59 -0500 Subject: [External] : Re: Guards In-Reply-To: <1925778026.1382732.1615326336812.JavaMail.zimbra@u-pem.fr> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <2136938599.1349657.1615314728393.JavaMail.zimbra@u-pem.fr> <9D092917-D5FD-46D4-9988-44A67B4A66B2@oracle.com> <1925778026.1382732.1615326336812.JavaMail.zimbra@u-pem.fr> Message-ID: <27734a67-d404-a560-5510-c90bb90a9021@oracle.com> > There are two kinds of input args, one is the implicit target, the others are other arguments. > It's not obvious to me that the parameters other than the target have to have access to the bindings. You keep saying "access to the bindings" as if that is a special thing.? Bindings are ordinary local variables.? Code has "access" to the bindings when the bindings are in scope.? There's nothing magic here. From john.r.rose at oracle.com Tue Mar 9 22:05:28 2021 From: john.r.rose at oracle.com (John Rose) Date: Tue, 9 Mar 2021 14:05:28 -0800 Subject: [External] : Re: Guards In-Reply-To: <1925778026.1382732.1615326336812.JavaMail.zimbra@u-pem.fr> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <2136938599.1349657.1615314728393.JavaMail.zimbra@u-pem.fr> <9D092917-D5FD-46D4-9988-44A67B4A66B2@oracle.com> <1925778026.1382732.1615326336812.JavaMail.zimbra@u-pem.fr> Message-ID: On Mar 9, 2021, at 1:45 PM, forax at univ-mlv.fr wrote: > > I see the pattern + parameters has a partial application, i.e. the parameters other than the target are constants, > the keys of the map are constant, the java.util.regex.Pattern (or the string) of a metch is a constant, etc > > So yes, a pattern have input parameters other than the target but should they have access to the bindings, i think we are loosing a lot with that model. If I read you correctly, you want patterns to be as statically scrutable as possible, so that translation strategies can boil together as much of the pattern as possible before first execution, typically using indy or condy to do ?advanced? configuration and optimization of statement logic. Moreover, I think you want patterns to *reject* non-constant operands, so that such configurations are reliably done before first execution. I whole-heartedly agree with the first goal, but I think the second would be a bridge too far, because various kinds of refactorings can shift expressions in and out of the ?constant? category, and that?s useful. I think we can trust users to use constants in places where they are advantageous. You may well ask, ?but what is the language support for reliably folding constants at javac translation time?? And the answer is ?nothing yet?. But I think that is a job for Brian?s constant-folding mechanisms, building on top of his ConstantDesc work. Until that point, we can live with non-constant expressions. I would say, before that point, it would be foolish to add indy support for constant in-args, just for and only for patterns. ? John From forax at univ-mlv.fr Tue Mar 9 22:57:15 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Tue, 9 Mar 2021 23:57:15 +0100 (CET) Subject: [External] : Re: Guards In-Reply-To: References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <2136938599.1349657.1615314728393.JavaMail.zimbra@u-pem.fr> <9D092917-D5FD-46D4-9988-44A67B4A66B2@oracle.com> <1925778026.1382732.1615326336812.JavaMail.zimbra@u-pem.fr> Message-ID: <346876789.1421270.1615330635738.JavaMail.zimbra@u-pem.fr> > De: "John Rose" > ?: "Remi Forax" > Cc: "Brian Goetz" , "amber-spec-experts" > > Envoy?: Mardi 9 Mars 2021 23:05:28 > Objet: Re: [External] : Re: Guards > On Mar 9, 2021, at 1:45 PM, [ mailto:forax at univ-mlv.fr | forax at univ-mlv.fr ] > wrote: >> I see the pattern + parameters has a partial application, i.e. the parameters >> other than the target are constants, >> the keys of the map are constant, the java.util.regex.Pattern (or the string) of >> a metch is a constant, etc >> So yes, a pattern have input parameters other than the target but should they >> have access to the bindings, i think we are loosing a lot with that model. > If I read you correctly, you want patterns to be > as statically scrutable as possible, so that translation > strategies can boil together as much of the pattern > as possible before first execution, typically using > indy or condy to do ?advanced? configuration > and optimization of statement logic. Not for the translation strategy, just to be able to rapidly read all the patterns without having to precisely follow the control flow. I would like to have the horizontal reading to be as simple as possible so you can grasp the whole pattern matching vertically. Another way to see it is to see Pattern as declarations more than expressions. > Moreover, I think you want patterns to *reject* > non-constant operands, so that such configurations > are reliably done before first execution. > I whole-heartedly agree with the first goal, > but I think the second would be a bridge too > far, because various kinds of refactorings > can shift expressions in and out of the > ?constant? category, and that?s useful. yes, it's not "constant" for the language. It's morally constant, the same way the lambdas in a stream has to be side effect free. [...] > ? John R?mi From john.r.rose at oracle.com Wed Mar 10 00:04:02 2021 From: john.r.rose at oracle.com (John Rose) Date: Tue, 9 Mar 2021 16:04:02 -0800 Subject: [External] : Re: Guards In-Reply-To: <346876789.1421270.1615330635738.JavaMail.zimbra@u-pem.fr> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <2136938599.1349657.1615314728393.JavaMail.zimbra@u-pem.fr> <9D092917-D5FD-46D4-9988-44A67B4A66B2@oracle.com> <1925778026.1382732.1615326336812.JavaMail.zimbra@u-pem.fr> <346876789.1421270.1615330635738.JavaMail.zimbra@u-pem.fr> Message-ID: <57823ADD-BCF6-4CEE-8DC4-A17E3070A0A0@oracle.com> On Mar 9, 2021, at 2:57 PM, forax at univ-mlv.fr wrote: > > > > De: "John Rose" > ?: "Remi Forax" > Cc: "Brian Goetz" , "amber-spec-experts" > Envoy?: Mardi 9 Mars 2021 23:05:28 > Objet: Re: [External] : Re: Guards > On Mar 9, 2021, at 1:45 PM, forax at univ-mlv.fr wrote: > > I see the pattern + parameters has a partial application, i.e. the parameters other than the target are constants, > the keys of the map are constant, the java.util.regex.Pattern (or the string) of a metch is a constant, etc > > So yes, a pattern have input parameters other than the target but should they have access to the bindings, i think we are loosing a lot with that model. > > If I read you correctly, you want patterns to be > as statically scrutable as possible, so that translation > strategies can boil together as much of the pattern > as possible before first execution, typically using > indy or condy to do ?advanced? configuration > and optimization of statement logic. > > Not for the translation strategy, just to be able to rapidly read all the patterns without having to precisely follow the control flow. > I would like to have the horizontal reading to be as simple as possible so you can grasp the whole pattern matching vertically. > Another way to see it is to see Pattern as declarations more than expressions. OK. So you are saying you are OK with northward dependencies on non-constant data (names of locals, etc.) but not westward. (BTW, we all agree that southward and westward dependencies are harder to cope with. Note, however, that Java declaration syntax has a prevailing eastward dependency, from the initializer. Just something to keep in mind for later.) When you say ?horizontal reading?, I note that one *does* read westward dependencies before reading content to the east, but I think you want the whole line to be readable at once. Well, that?s fine, but it?s not clear that is a hard constraint on what we are designing. A coder can code that way. Even with guards, the ?&&G? part can and sometimes should be put on its own line, south of the pattern. I can derive a soft requirement out of your point, and that is that ?it would be nice? if ?westward? dependencies in patterns are hard to hide. The ?&&? operator fulfills this requirement for guards, since ?&&? is relatively bold in appearance (easy to spot) and is not used for patterns. This is also the reason I (very tentatively) suggested that a unary version of the ?&&? syntax might also be useful for marking in-arguments inside of patterns. If that suggestion doesn?t fly, maybe we can come up with some other marker for pattern in-args in general, or maybe just for ?westward in-args in the same pattern?, that serves the purpose of making them stand out. (And without committing the newbie error of Always Making the NEW Feature **Stand OUT**, with h/t to Neal Gafter.) Happily, we can solve the general problem of in-arg marking separately from this design iteration. ? John From guy.steele at oracle.com Wed Mar 10 00:50:58 2021 From: guy.steele at oracle.com (Guy Steele) Date: Tue, 9 Mar 2021 19:50:58 -0500 Subject: [External] : Re: Two new draft pattern matching JEPs In-Reply-To: <2128183471.1343559.1615313875965.JavaMail.zimbra@u-pem.fr> References: <902390849.1824581.1614775817744.JavaMail.zimbra@u-pem.fr> <2128183471.1343559.1615313875965.JavaMail.zimbra@u-pem.fr> Message-ID: <1B051F27-FB30-441A-9248-38748F311DFE@oracle.com> > On Mar 9, 2021, at 1:17 PM, forax at univ-mlv.fr wrote: >> . . . >> Now this example is not exactly analogous to your original, because we have not >> provided explicit variables for this purpose. I believe that in an earlier >> version of the design one would write >> >> case Rectangle(Point(int x1, int y1), Point(int x2, int y2)) >> >> But perhaps in the current proposal one must write >> >> case Rectangle(Point(int x1, int y1) & var p1, Point(int x2, int y2) & var p2) >> >> or perhaps >> >> case Rectangle(var p1 & Point(int x1, int y1), var p2 & Point(int x2, int y2)) >> >> In all of these cases, my argument is still the same: the simplest model is that >> that deconstructor for Rectangle just supplies two values that are points, and >> then the first point value is matched against the first sub pattern, and only >> then is the second point value matched against the second subpattern. As a >> result p2 and x2 and y2 do not yet have bindings or values while the first >> sub-pattern is being matched. > > I think we agree here, i was just saying that for a deconstructor call you get all the bindings at the same time, > so in the case there is two bindings, having two expressions that each one guard one binding is equivalent to have one guard that uses the two bindings. > > Obviously, when patterns are nested, you don't have access to all the bindings of all the patterns at once. > > Now, i don't think you have to use an & between patterns to provide a name, in my opinion, we should things in the other way > case Rectangle(Point(int x1, int y1), Point(int x2, int y2)) > should be a simplified way to write > case Rectangle(Point(int x1, int y1) _, Point(int x2, int y2) _) > i.e. when destructuring, we don't provide a name for that binding (hence my use of '_') > > With that in mind, if you want to name the intermediary point, you can just write > case Rectangle(Point(int x1, int y1) p1, Point(int x2, int y2) p2) Yes, then I think we do agree here. We may have had a misunderstanding over language: I usually understand ?binding? to mean the association of a value with a variable. I think that for a deconstructor you get all the _values_ at the same time, after which _bindings_ of variables to those values (or matching of sub patterns to those values) are established (or performed) sequentially. But this is a very delicate distinction that I am drawing. ?Guy From guy.steele at oracle.com Wed Mar 10 00:55:25 2021 From: guy.steele at oracle.com (Guy Steele) Date: Tue, 9 Mar 2021 19:55:25 -0500 Subject: [External] : Re: Guards In-Reply-To: References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <2136938599.1349657.1615314728393.JavaMail.zimbra@u-pem.fr> Message-ID: <16FFA564-33D9-44BE-A6BA-10B1ECF0E6CD@oracle.com> > On Mar 9, 2021, at 1:47 PM, Brian Goetz wrote: > > >> Apart from what have said about letting grobble to fully access to the bindings > > Except that argument doesn't make sense. Accessing the bindings is not a special behavior of grobble, but a natural consequence of flow scoping. If I have P && g (or P & grobble(g)), then the scoping rules will tell us that the true set of P is present in g, and we're done. Nothing special here. Well, just to be fair, rules for flow scoping within expressions g && h already exist, but rules for P && g and P & Q do not yet exist. It?s easy to see that they probably should exist, and that this can be done in a manner entirely analogous to the way flow scoping is already done in expressions, but it has been an implicit assumption in some of the past discussion that is worth making explicit. ?Consider it done!? :-) ?Guy From gavin.bierman at oracle.com Wed Mar 10 09:46:41 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Wed, 10 Mar 2021 09:46:41 +0000 Subject: [External] : Re: Guards In-Reply-To: <16FFA564-33D9-44BE-A6BA-10B1ECF0E6CD@oracle.com> References: <7deeb8d8-4b00-4bfa-6aa3-46af8e08af21@oracle.com> <2136938599.1349657.1615314728393.JavaMail.zimbra@u-pem.fr> <16FFA564-33D9-44BE-A6BA-10B1ECF0E6CD@oracle.com> Message-ID: <35BCFC37-6E87-4DBC-B880-B23A221587C6@oracle.com> On 10 Mar 2021, at 00:55, Guy Steele > wrote: On Mar 9, 2021, at 1:47 PM, Brian Goetz > wrote: Apart from what have said about letting grobble to fully access to the bindings Except that argument doesn't make sense. Accessing the bindings is not a special behavior of grobble, but a natural consequence of flow scoping. If I have P && g (or P & grobble(g)), then the scoping rules will tell us that the true set of P is present in g, and we're done. Nothing special here. Well, just to be fair, rules for flow scoping within expressions g && h already exist, but rules for P && g and P & Q do not yet exist. It?s easy to see that they probably should exist, and that this can be done in a manner entirely analogous to the way flow scoping is already done in expressions, but it has been an implicit assumption in some of the past discussion that is worth making explicit. ?Consider it done!? :-) The draft JEP does try to be reasonably informative on this matter: A conditional-and pattern p & q introduces the union of the pattern variables introduced by the patterns p and q. The scope of any pattern variable declaration in p includes the pattern q. (This allows for a pattern such as String s & true(s.length() > 1) -- a value matches this pattern if it can be cast to a String and that string has a length of two or more characters.) Gavin From gavin.bierman at oracle.com Wed Mar 10 14:47:30 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Wed, 10 Mar 2021 14:47:30 +0000 Subject: Guards redux Message-ID: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> Okay, so it seems like our initial stab at guards and a pattern conjunction operator needs some finessing. Here's another take, inspired by Guy's emails. Step 1. Let's use `p && e` as the way to guard a pattern p with a boolean expression e. Step 2. [Now or later] Let's use `&` (and `|`) as the conjunction and disjunction operators on patterns. There are a couple of immediate parsing puzzlers: * e instanceof String s && s.length() > 2 This parses as `(e instanceof String s) && s.length() > 2` today. We need to be careful that our grammar continues to make this the case (see below). We will also introduce a parenthesized pattern, which you can use if you want the dangling `&& s.length() > 2` to parse as a guard for the `String s` type pattern. (It is extremely fortuitous that the functional behavior of both expressions is the same, but either way I think this is a simple rule.) * case p && b -> c -> b Now we have some ambiguity from the `->` in a lambda expression and in a switch rule. Fortunately, again I think we can just lean into the grammar to get what we want. At the moment, the grammar for expressions is: Expression: LambdaExpression AssignmentExpression As a lambda expression can never be a boolean expression it can never meaningfully serve as a guard for a pattern. Great! So, I'd like to suggest this grammar for patterns (including pattern conjunction and pattern disjunction operators for completeness but we can drop them from the first release): Pattern: : ConditionalOrPattern : ConditionalOrPattern `&&` Guard ConditionalOrPattern: : ConditionalAndPattern : ConditionalOrPattern `|` ConditionalAndPattern ConditionalAndPattern: : PrimaryPattern : ConditionalAndPattern `&` PrimaryPattern PrimaryPattern: : TypePattern : RecordPattern : ArrayPattern : `(` Pattern `)` Guard: : AssignmentExpression Along with the following change to the grammar for instanceof: InstanceofExpression: : RelationalExpression `instanceof` ReferenceType : RelationalExpression `instanceof` PrimaryPattern <-- Note! Some consequences: p1 & p2 & p3 && g1 && g2 parses as ((p1 & p2) & p3) && (g1 && g2), yay! p1 && g1 & p2 && g2 needs to be bracketed as (p1 && g1) & (p2 && g2) to parse properly. But that's okay, as I think the second is much clearer. Let me know what you think. Gavin From brian.goetz at oracle.com Wed Mar 10 15:02:10 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 10 Mar 2021 10:02:10 -0500 Subject: Guards redux In-Reply-To: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> Message-ID: This feels like we landed in a good place.? It preserves the underlying goal of the original approach -- that we can compose patterns with patterns, and patterns with boolean expressions, and doesn't require nailing bags onto switch (yay).? The main difference is that it creates a distinguished "guarded pattern" operator rather than asking users to compose guarded patterns from an expression-to-pattern operator and pattern-AND. The main objections to the `P & true(e)` approach were the aesthetic reaction to this use of `true` (which was one of those first-five-minutes reactions, and people might well have gotten over it in the next five minutes), and the more serious discoverability problem of having to compose two (currently unfamiliar) features to get guarded patterns.? The current proposal seems a lower energy state, while deriving from the same basic principles. FTR, the key observation that broke the jam was the observation that, if we treat & and && as: ??? (&) :: Pattern -> Pattern -> Pattern ??? (&&) :: Pattern -> Expression -> Pattern we can, with parentheses, achieve arbitrary orderings of patterns and guard expressions, and are not forced to push all guards to the end (which was where we got stuck the last time we looked at &&.) On 3/10/2021 9:47 AM, Gavin Bierman wrote: > Okay, so it seems like our initial stab at guards and a pattern conjunction > operator needs some finessing. > > Here's another take, inspired by Guy's emails. > > Step 1. Let's use `p && e` as the way to guard a pattern p with a boolean > expression e. > > Step 2. [Now or later] Let's use `&` (and `|`) as the conjunction and > disjunction operators on patterns. > > There are a couple of immediate parsing puzzlers: > > * e instanceof String s && s.length() > 2 > > This parses as `(e instanceof String s) && s.length() > 2` today. We need to be > careful that our grammar continues to make this the case (see below). We will > also introduce a parenthesized pattern, which you can use if you want the > dangling `&& s.length() > 2` to parse as a guard for the `String s` type > pattern. (It is extremely fortuitous that the functional behavior of both > expressions is the same, but either way I think this is a simple rule.) > > * case p && b -> c -> b > > Now we have some ambiguity from the `->` in a lambda expression and in a switch > rule. Fortunately, again I think we can just lean into the grammar to get what > we want. At the moment, the grammar for expressions is: > > Expression: > LambdaExpression > AssignmentExpression > > As a lambda expression can never be a boolean expression it can never > meaningfully serve as a guard for a pattern. Great! > > So, I'd like to suggest this grammar for patterns (including pattern conjunction > and pattern disjunction operators for completeness but we can drop them from the > first release): > > Pattern: > : ConditionalOrPattern > : ConditionalOrPattern `&&` Guard > > ConditionalOrPattern: > : ConditionalAndPattern > : ConditionalOrPattern `|` ConditionalAndPattern > > ConditionalAndPattern: > : PrimaryPattern > : ConditionalAndPattern `&` PrimaryPattern > > PrimaryPattern: > : TypePattern > : RecordPattern > : ArrayPattern > : `(` Pattern `)` > > Guard: > : AssignmentExpression > > Along with the following change to the grammar for instanceof: > > InstanceofExpression: > : RelationalExpression `instanceof` ReferenceType > : RelationalExpression `instanceof` PrimaryPattern <-- Note! > > Some consequences: > > p1 & p2 & p3 && g1 && g2 parses as ((p1 & p2) & p3) && (g1 && g2), yay! > > p1 && g1 & p2 && g2 needs to be bracketed as (p1 && g1) & (p2 && g2) to parse > properly. But that's okay, as I think the second is much clearer. > > Let me know what you think. > > Gavin From forax at univ-mlv.fr Wed Mar 10 16:18:15 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 10 Mar 2021 17:18:15 +0100 (CET) Subject: Guards redux In-Reply-To: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> Message-ID: <550604787.2090798.1615393095931.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Gavin Bierman" > ?: "amber-spec-experts" > Envoy?: Mercredi 10 Mars 2021 15:47:30 > Objet: Guards redux > Okay, so it seems like our initial stab at guards and a pattern conjunction > operator needs some finessing. > > Here's another take, inspired by Guy's emails. > > Step 1. Let's use `p && e` as the way to guard a pattern p with a boolean > expression e. > > Step 2. [Now or later] Let's use `&` (and `|`) as the conjunction and > disjunction operators on patterns. Using & between patterns is unfortunate given that if if first pattern is not total and does not match, we will not "execute" the second pattern. So the semantics is lazy but currently '&' means non-lazy on expressions. > > There are a couple of immediate parsing puzzlers: > > * e instanceof String s && s.length() > 2 > > This parses as `(e instanceof String s) && s.length() > 2` today. We need to be > careful that our grammar continues to make this the case (see below). We will > also introduce a parenthesized pattern, which you can use if you want the > dangling `&& s.length() > 2` to parse as a guard for the `String s` type > pattern. (It is extremely fortuitous that the functional behavior of both > expressions is the same, but either way I think this is a simple rule.) > > * case p && b -> c -> b > > Now we have some ambiguity from the `->` in a lambda expression and in a switch > rule. Fortunately, again I think we can just lean into the grammar to get what > we want. At the moment, the grammar for expressions is: > > Expression: > LambdaExpression > AssignmentExpression > > As a lambda expression can never be a boolean expression it can never > meaningfully serve as a guard for a pattern. Great! > > So, I'd like to suggest this grammar for patterns (including pattern conjunction > and pattern disjunction operators for completeness but we can drop them from the > first release): > > Pattern: > : ConditionalOrPattern > : ConditionalOrPattern `&&` Guard > > ConditionalOrPattern: > : ConditionalAndPattern > : ConditionalOrPattern `|` ConditionalAndPattern > > ConditionalAndPattern: > : PrimaryPattern > : ConditionalAndPattern `&` PrimaryPattern > > PrimaryPattern: > : TypePattern > : RecordPattern > : ArrayPattern > : `(` Pattern `)` > > Guard: > : AssignmentExpression > > Along with the following change to the grammar for instanceof: > > InstanceofExpression: > : RelationalExpression `instanceof` ReferenceType > : RelationalExpression `instanceof` PrimaryPattern <-- Note! > > Some consequences: > > p1 & p2 & p3 && g1 && g2 parses as ((p1 & p2) & p3) && (g1 && g2), yay! > > p1 && g1 & p2 && g2 needs to be bracketed as (p1 && g1) & (p2 && g2) to parse > properly. But that's okay, as I think the second is much clearer. > > Let me know what you think. > > Gavin From forax at univ-mlv.fr Wed Mar 10 16:33:03 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 10 Mar 2021 17:33:03 +0100 (CET) Subject: Guards redux In-Reply-To: References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> Message-ID: <637699712.2107687.1615393983786.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Gavin Bierman" , "amber-spec-experts" > > Envoy?: Mercredi 10 Mars 2021 16:02:10 > Objet: Re: Guards redux > This feels like we landed in a good place. It preserves the underlying goal of > the original approach -- that we can compose patterns with patterns, and > patterns with boolean expressions, and doesn't require nailing bags onto switch > (yay). The main difference is that it creates a distinguished "guarded pattern" > operator rather than asking users to compose guarded patterns from an > expression-to-pattern operator and pattern-AND. You nail the guard to a pattern, which is equivalent until we have nested patterns (and "or"/"and" patterns). I see a lot of advantages of using && to link a guard to a pattern, - the symbol is heavy so there is a clear visual separation - without any supplementary parenthesis, && after the type pattern in an instanceofis the && between expression, it's almost like you can not have a guard with an instanceof, in practice, few instanceof will have a guard. I still think that using a guard inside a nested pattern is ugly but it can be just that, ugly. Someone may want a short-circuit in a deeply nested patterns . > The main objections to the `P & true(e)` approach were the aesthetic reaction to > this use of `true` (which was one of those first-five-minutes reactions, and > people might well have gotten over it in the next five minutes), and the more > serious discoverability problem of having to compose two (currently unfamiliar) > features to get guarded patterns. The current proposal seems a lower energy > state, while deriving from the same basic principles. > FTR, the key observation that broke the jam was the observation that, if we > treat & and && as: > (&) :: Pattern -> Pattern -> Pattern > (&&) :: Pattern -> Expression -> Pattern > we can, with parentheses, achieve arbitrary orderings of patterns and guard > expressions, and are not forced to push all guards to the end (which was where > we got stuck the last time we looked at &&.) As i said to Gavin, i'm not at ease with using the symbol '&' in between patterns. R?mi > On 3/10/2021 9:47 AM, Gavin Bierman wrote: >> Okay, so it seems like our initial stab at guards and a pattern conjunction >> operator needs some finessing. >> Here's another take, inspired by Guy's emails. >> Step 1. Let's use `p && e` as the way to guard a pattern p with a boolean >> expression e. >> Step 2. [Now or later] Let's use `&` (and `|`) as the conjunction and >> disjunction operators on patterns. >> There are a couple of immediate parsing puzzlers: >> * e instanceof String s && s.length() > 2 >> This parses as `(e instanceof String s) && s.length() > 2` today. We need to be >> careful that our grammar continues to make this the case (see below). We will >> also introduce a parenthesized pattern, which you can use if you want the >> dangling `&& s.length() > 2` to parse as a guard for the `String s` type >> pattern. (It is extremely fortuitous that the functional behavior of both >> expressions is the same, but either way I think this is a simple rule.) >> * case p && b -> c -> b >> Now we have some ambiguity from the `->` in a lambda expression and in a switch >> rule. Fortunately, again I think we can just lean into the grammar to get what >> we want. At the moment, the grammar for expressions is: >> Expression: >> LambdaExpression >> AssignmentExpression >> As a lambda expression can never be a boolean expression it can never >> meaningfully serve as a guard for a pattern. Great! >> So, I'd like to suggest this grammar for patterns (including pattern conjunction >> and pattern disjunction operators for completeness but we can drop them from the >> first release): >> Pattern: >> : ConditionalOrPattern >> : ConditionalOrPattern `&&` Guard >> ConditionalOrPattern: >> : ConditionalAndPattern >> : ConditionalOrPattern `|` ConditionalAndPattern >> ConditionalAndPattern: >> : PrimaryPattern >> : ConditionalAndPattern `&` PrimaryPattern >> PrimaryPattern: >> : TypePattern >> : RecordPattern >> : ArrayPattern >> : `(` Pattern `)` >> Guard: >> : AssignmentExpression >> Along with the following change to the grammar for instanceof: >> InstanceofExpression: >> : RelationalExpression `instanceof` ReferenceType >> : RelationalExpression `instanceof` PrimaryPattern <-- Note! >> Some consequences: >> p1 & p2 & p3 && g1 && g2 parses as ((p1 & p2) & p3) && (g1 && g2), yay! >> p1 && g1 & p2 && g2 needs to be bracketed as (p1 && g1) & (p2 && g2) to parse >> properly. But that's okay, as I think the second is much clearer. >> Let me know what you think. >> Gavin From brian.goetz at oracle.com Wed Mar 10 16:40:33 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 10 Mar 2021 11:40:33 -0500 Subject: Guards redux In-Reply-To: <550604787.2090798.1615393095931.JavaMail.zimbra@u-pem.fr> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <550604787.2090798.1615393095931.JavaMail.zimbra@u-pem.fr> Message-ID: <21a24316-e6bc-88ca-2c41-77704653c6a1@oracle.com> > Using & between patterns is unfortunate given that if if first pattern is not total and does not match, we will not "execute" the second pattern. > So the semantics is lazy but currently '&' means non-lazy on expressions. While I understand the discomfort that drives you to make this observation, I think we should be a little more careful with our terms; lazy is not the right word here.? (When used this way, *ALL* patterns are lazy!? A pattern is not an expression; it is not "evaluated" like expressions are.? It is a _recipe_ for a deferred computation (like a lambda.)) When we say `x instanceof (P&Q)`, we "construct" the composite pattern P&Q _before we "execute" it_.? What are the semantics of such a combined pattern?? It is short-circuiting; when we later compute whether a target matches the pattern, we first try to match it to the first subpattern, and if that succeeds, we try to match it to the second subpattern.? This is not "lazy evaluation", this is just the semantics of pattern composition with AND. Short-circuiting is a more accurate description here than laziness. As an analogy, imagine we had intersection types in instanceof (we already do for casts, so this is not much of a leap).? If we asked `x instanceof T&U`, and x was not an instanceof T, would we insist that we also test against U (which could throw if the JVM can't find U.class to load!)? Or would we accept that a test against the intersection type T&U can reasonably "short circuit" the test if it fails the first one?? Of course we would.? Why is it different for patterns, which are a generalization of this kind of test? Continuing on the "like a lambda" analogy, if pattern refers to variables in the current scope as one of its "inputs", this is like a capturing lambda.? And, like a lambda, when we were looking at `true(e)`, we said that any locals in `e` should be effectively final. So to propagate that constraint into the current model: in a guarded pattern P&&g, all locals referred to in `g` should be effectively final.? (We've already said this of pattern input parameters in general.) From brian.goetz at oracle.com Wed Mar 10 17:04:21 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 10 Mar 2021 12:04:21 -0500 Subject: [External] : Re: Guards redux In-Reply-To: <637699712.2107687.1615393983786.JavaMail.zimbra@u-pem.fr> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <637699712.2107687.1615393983786.JavaMail.zimbra@u-pem.fr> Message-ID: > You nail the guard to a pattern, which is equivalent until we have > nested patterns (and "or"/"and" patterns). We have nested patterns already in the JEPs on the table.? Where's the problem? > I see a lot of advantages of using && to link a guard to a pattern, > - the symbol is heavy so there is a clear visual separation > - without any supplementary parenthesis, && after the type pattern in > an instanceofis the && between expression, it's almost like you can > not have a guard with an instanceof, in practice, few instanceof will > have a guard. I agree few instanceof will have a guard, but users are still free to express it that way if they like, and there's nothing wrong with that. > I still think that using a guard inside a nested pattern is ugly but > it can be just that, ugly. Someone may want a short-circuit in a > deeply nested patterns . Yes.? This is not unlike other compositions; for any compositional tool, you can overuse it.? (You can arbitrarily compose boolean expressions (or arbitrarily chain method invocations), but sometimes this is taking it too far.) > As i said to Gavin, i'm not at ease with using the symbol '&' in > between patterns. I think that this is mostly a "who moved my cheese" reaction; you're used to thinking that & is just for bitwise operations.? But, that's not actually true; we already use & and | on types -- intersection type casts, additional generic type bounds, multi-catch.? This appeals to a notion that & and | are boolean-like combinators on types (even if not exposed in all places they might make sense), but this is a different kind of combination than on integers.? And yet a different kind of combination on patterns.? (In an alternate universe, we might have different symbols for adding ints vs floats vs string concatenation, but + works well enough that overloading the symbols is OK -- because using + in this way appeals to the underlying algebraic monoid structure these types share.) The reason that & and | make sense on patterns, and on types, is that, like the more familiar versions on bits, they describe a _boolean algebra_.? Boolean algebras have familiar properties such as De Morgan's Laws.? These work for types (when interpreted as value sets) as well as bits, and they work for patterns too. I think where you're getting hung up is that when patterns produce bindings, and other patterns consume those bindings, we have a dataflow dependence which would appear to undermine certain other expected properties of a boolean algebra, such as commutativity. But, if we view those dataflow dependencies as a separate constraint -- as we *already do* for ints (e.g., `(x=3)&(x|4)`, is invalid when `x` is an DU int, but valid when `x` is DA), this seeming contradiction vanishes, and is seen to be merely a post-hoc well-formedness constraint.? If the WF constraint is satisfied, the expected properties of boolean algebras (associativity, commutativity, absorption, etc) are satisfied too. From forax at univ-mlv.fr Wed Mar 10 17:04:40 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 10 Mar 2021 18:04:40 +0100 (CET) Subject: Guards redux In-Reply-To: <21a24316-e6bc-88ca-2c41-77704653c6a1@oracle.com> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <550604787.2090798.1615393095931.JavaMail.zimbra@u-pem.fr> <21a24316-e6bc-88ca-2c41-77704653c6a1@oracle.com> Message-ID: <151193990.2125842.1615395880442.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" , "Gavin Bierman" > Cc: "amber-spec-experts" > Envoy?: Mercredi 10 Mars 2021 17:40:33 > Objet: Re: Guards redux >> Using & between patterns is unfortunate given that if if first pattern is not >> total and does not match, we will not "execute" the second pattern. >> So the semantics is lazy but currently '&' means non-lazy on expressions. > > While I understand the discomfort that drives you to make this > observation, I think we should be a little more careful with our terms; > lazy is not the right word here.? (When used this way, *ALL* patterns > are lazy!? A pattern is not an expression; it is not "evaluated" like > expressions are.? It is a _recipe_ for a deferred computation (like a > lambda.)) Yes, that the reason why i'm not at ease with the fact that a pattern can use the value of the bindings as inputs. This also remember me why i want a reference to a pattern method to use '::' instead of '.' > > When we say `x instanceof (P&Q)`, we "construct" the composite pattern > P&Q _before we "execute" it_.? What are the semantics of such a combined > pattern?? It is short-circuiting; when we later compute whether a target > matches the pattern, we first try to match it to the first subpattern, > and if that succeeds, we try to match it to the second subpattern.? This > is not "lazy evaluation", this is just the semantics of pattern > composition with AND. Short-circuiting is a more accurate description > here than laziness. > > As an analogy, imagine we had intersection types in instanceof (we > already do for casts, so this is not much of a leap).? If we asked `x > instanceof T&U`, and x was not an instanceof T, would we insist that we > also test against U (which could throw if the JVM can't find U.class to > load!)? Or would we accept that a test against the intersection type T&U > can reasonably "short circuit" the test if it fails the first one?? Of > course we would.? Why is it different for patterns, which are a > generalization of this kind of test ? Ok, i think you are right. > > Continuing on the "like a lambda" analogy, if pattern refers to > variables in the current scope as one of its "inputs", this is like a > capturing lambda.? And, like a lambda, when we were looking at > `true(e)`, we said that any locals in `e` should be effectively final. > > So to propagate that constraint into the current model: in a guarded > pattern P&&g, all locals referred to in `g` should be effectively > final.? (We've already said this of pattern input parameters in general.) yes, not allowing a i++ in a middle of a guard is a good idea. R?mi From guy.steele at oracle.com Wed Mar 10 18:52:18 2021 From: guy.steele at oracle.com (Guy Steele) Date: Wed, 10 Mar 2021 13:52:18 -0500 Subject: Guards redux In-Reply-To: <21a24316-e6bc-88ca-2c41-77704653c6a1@oracle.com> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <550604787.2090798.1615393095931.JavaMail.zimbra@u-pem.fr> <21a24316-e6bc-88ca-2c41-77704653c6a1@oracle.com> Message-ID: See below. > On Mar 10, 2021, at 11:40 AM, Brian Goetz wrote: > > >> Using & between patterns is unfortunate given that if if first pattern is not total and does not match, we will not "execute" the second pattern. >> So the semantics is lazy but currently '&' means non-lazy on expressions. > > While I understand the discomfort that drives you to make this observation, I think we should be a little more careful with our terms; lazy is not the right word here. Yes, ?lazy? is not quite the right word here, but I suspect R?mi intends to use that word to mean what you and I use the term ?short-circuiting? to refer to. The notions are of course closely related, because in a lazy language, short-circuiting falls out naturally from a straightforward definition such as a && b = a ? b : false But of course Java is not a lazy language. One can instead model short-circuiting behavior in terms of lambdas: a && b is an abbreviation for conditionalAnd(a, () -> b) conditionalAnd(x, y) = x ? y() : false Nevertheless, R?mi?s main point remains: it is true that `&&` makes the average Java programmer think of conditional execution and `&` does not. That makes me a bit uncomfortable also, but for me, at least, that discomfort is outweighed by other considerations. There are significant advantages to using distinct symbols for pattern conjunction and guard attachment, and, despite having proposed the use of `&&&` earlier, I find I cannot stomach it myself?it would be too verbose a symbol for pattern conjunction. So I regard the choice of `&` for pattern conjunction as a compromise, but an acceptable one, largely because I think that in practice programmers will rarely rely on the conditional-execution aspect of pattern conjunction. [more below] > (When used this way, *ALL* patterns are lazy! A pattern is not an expression; it is not "evaluated" like expressions are. It is a _recipe_ for a deferred computation (like a lambda.)) > > When we say `x instanceof (P&Q)`, we "construct" the composite pattern P&Q _before we "execute" it_. What are the semantics of such a combined pattern? It is short-circuiting; when we later compute whether a target matches the pattern, we first try to match it to the first subpattern, and if that succeeds, we try to match it to the second subpattern. This is not "lazy evaluation", this is just the semantics of pattern composition with AND. Short-circuiting is a more accurate description here than laziness. > > As an analogy, imagine we had intersection types in instanceof (we already do for casts, so this is not much of a leap). If we asked `x instanceof T&U`, and x was not an instanceof T, would we insist that we also test against U (which could throw if the JVM can't find U.class to load!) Or would we accept that a test against the intersection type T&U can reasonably "short circuit" the test if it fails the first one? Of course we would. Why is it different for patterns, which are a generalization of this kind of test? One reason it is different for patterns is that patterns can contain expressions (arguments for any input parameter). Unless we are in a position to enforce that such expressions are free of side effects, the programmer will have an interest in being sure that pattern conjunction either definitely short-circuits or definitely does not short-circuit. In the case of intersection types, it?s just a speed optimization. > Continuing on the "like a lambda" analogy, if pattern refers to variables in the current scope as one of its "inputs", this is like a capturing lambda. And, like a lambda, when we were looking at `true(e)`, we said that any locals in `e` should be effectively final. > > So to propagate that constraint into the current model: in a guarded pattern P&&g, all locals referred to in `g` should be effectively final. (We've already said this of pattern input parameters in general.) > > From guy.steele at oracle.com Wed Mar 10 19:10:28 2021 From: guy.steele at oracle.com (Guy Steele) Date: Wed, 10 Mar 2021 14:10:28 -0500 Subject: [External] : Re: Guards redux In-Reply-To: References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <637699712.2107687.1615393983786.JavaMail.zimbra@u-pem.fr> Message-ID: <8FAF4FB1-9070-49E9-88B8-F16A9163796E@oracle.com> > On Mar 10, 2021, at 12:04 PM, Brian Goetz wrote: > > > >> You nail the guard to a pattern, which is equivalent until we have nested patterns (and "or"/"and" patterns). > > We have nested patterns already in the JEPs on the table. Where's the problem? > >> I see a lot of advantages of using && to link a guard to a pattern, >> - the symbol is heavy so there is a clear visual separation >> - without any supplementary parenthesis, && after the type pattern in an instanceofis the && between expression, it's almost like you can not have a guard with an instanceof, in practice, few instanceof will have a guard. > > I agree few instanceof will have a guard, but users are still free to express it that way if they like, and there's nothing wrong with that. > >> I still think that using a guard inside a nested pattern is ugly but it can be just that, ugly. Someone may want a short-circuit in a deeply nested patterns . > > Yes. This is not unlike other compositions; for any compositional tool, you can overuse it. (You can arbitrarily compose boolean expressions (or arbitrarily chain method invocations), but sometimes this is taking it too far.) > >> As i said to Gavin, i'm not at ease with using the symbol '&' in between patterns. > > I think that this is mostly a "who moved my cheese" reaction; you're used to thinking that & is just for bitwise operations. But, that's not actually true; we already use & and | on types -- intersection type casts, additional generic type bounds, multi-catch. This appeals to a notion that & and | are boolean-like combinators on types (even if not exposed in all places they might make sense), but this is a different kind of combination than on integers. And yet a different kind of combination on patterns. (In an alternate universe, we might have different symbols for adding ints vs floats vs string concatenation, but + works well enough that overloading the symbols is OK -- because using + in this way appeals to the underlying algebraic monoid structure these types share.) We all know that `+` is not your best poster child for this argument. (?foo? + 1) + 2 produces ?foo12? ?foo? + (1 + 2) produces ?foo3? which is not my idea of good monoid behavior. But there is a lot of wiggle room in your use of the word ?appeals?. :-) > The reason that & and | make sense on patterns, and on types, is that, like the more familiar versions on bits, they describe a _boolean algebra_. Boolean algebras have familiar properties such as De Morgan's Laws. These work for types (when interpreted as value sets) as well as bits, and they work for patterns too. > > I think where you're getting hung up is that when patterns produce bindings, and other patterns consume those bindings, we have a dataflow dependence which would appear to undermine certain other expected properties of a boolean algebra, such as commutativity. But, if we view those dataflow dependencies as a separate constraint -- as we *already do* for ints (e.g., `(x=3)&(x|4)`, is invalid when `x` is an DU int, but valid when `x` is DA), this seeming contradiction vanishes, and is seen to be merely a post-hoc well-formedness constraint. If the WF constraint is satisfied, the expected properties of boolean algebras (associativity, commutativity, absorption, etc) are satisfied too. And yet, even if we stipulate all that, it is still the case that in expressions, Java uses `&&` to indicate short-circuiting and `&` to indicate no reliance on short-circuiting, and this is long-standing, familiar use. For the other applications cited (intersection type casts, additional generic type bounds, multi-catch) this distinction does not matter. But in principle it does matter for patterns, because while patterns arguably do not involve _evaluation_, they most certainly involve _execution_ of possibly user-written code. If side effects can occur, the distinction arguably matters, and this is worth recognizing as we debate the design. Nevertheless, as I just wrote in an earlier email, I think that the desire to maintain this distinction should be outweighed by other considerations (visual analogy to type intersection, for which `&` is used but not `&&`; need for distinct symbols for pattern conjunction action and guard attachment in order to solve parsing problems; desire not to invent an arbitrary new symbol such as `&:` or `&&&`). From guy.steele at oracle.com Wed Mar 10 19:29:48 2021 From: guy.steele at oracle.com (Guy Steele) Date: Wed, 10 Mar 2021 14:29:48 -0500 Subject: Guards redux In-Reply-To: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> Message-ID: <7161794F-BBBA-4243-89AD-7C0F8DCC1766@oracle.com> > On Mar 10, 2021, at 9:47 AM, Gavin Bierman wrote: > > Okay, so it seems like our initial stab at guards and a pattern conjunction > operator needs some finessing. > > Here's another take, inspired by Guy's emails. > > Step 1. Let's use `p && e` as the way to guard a pattern p with a boolean > expression e. > > Step 2. [Now or later] Let's use `&` (and `|`) as the conjunction and > disjunction operators on patterns. > > There are a couple of immediate parsing puzzlers: > > * e instanceof String s && s.length() > 2 > > This parses as `(e instanceof String s) && s.length() > 2` today. We need to be > careful that our grammar continues to make this the case (see below). We will > also introduce a parenthesized pattern, which you can use if you want the > dangling `&& s.length() > 2` to parse as a guard for the `String s` type > pattern. (It is extremely fortuitous that the functional behavior of both > expressions is the same, but either way I think this is a simple rule.) > > * case p && b -> c -> b > > Now we have some ambiguity from the `->` in a lambda expression and in a switch > rule. Fortunately, again I think we can just lean into the grammar to get what > we want. At the moment, the grammar for expressions is: > > Expression: > LambdaExpression > AssignmentExpression > > As a lambda expression can never be a boolean expression it can never > meaningfully serve as a guard for a pattern. Great! Good catch here by Gavin; I had completely overlooked this potential difficulty with `->`. > So, I'd like to suggest this grammar for patterns (including pattern conjunction > and pattern disjunction operators for completeness but we can drop them from the > first release): > > Pattern: > : ConditionalOrPattern > : ConditionalOrPattern `&&` Guard Let me suggest this alternative: Pattern: : ConditionalOrPattern : ConditionalAndPattern `&&` Guard It is certainly true that in Java, `|` has higher precedence than `&&`, but I regard this as a pitfall for the programmer, an unfortunate fact inherited from C that does not cause such trouble in Java because (a) programmers tend not to mix the use of `&` and `|` on the one hand and `&&` and `||` on the other in boolean expressions, and (b) Java fortunately does not allow integers to be used as if they were booleans, so any mixture of `&` and `|` used on integers with use of `&&` and `||` is likely to be mediated by relational operators, and perhaps also parenthesized. But if the syntax of patterns includes `|` for pattern disjunction, the programmers will be positively encouraged to use mixtures of `|` and `&&`, and I think this is likely to lead to confusion. Consider: case Map.with(key1)(var theValue) | Map.with(key2)(var theValue) && theValue > 0: Question: does the guard apply if the pattern Map.with(key1)(var theValue) successfully matches? I believe the grammar adjustment I propose above would force the programmer to clarify by using parentheses: case (Map.with(key1)(var theValue) | Map.with(key2)(var theValue)) && theValue > 0: or case Map.with(key1)(var theValue) | (Map.with(key2)(var theValue) && theValue > 0): > ConditionalOrPattern: > : ConditionalAndPattern > : ConditionalOrPattern `|` ConditionalAndPattern > > ConditionalAndPattern: > : PrimaryPattern > : ConditionalAndPattern `&` PrimaryPattern > > PrimaryPattern: > : TypePattern > : RecordPattern > : ArrayPattern > : `(` Pattern `)` > > Guard: > : AssignmentExpression Yes, ?AssignmentExpression? is the right thing here. > Along with the following change to the grammar for instanceof: > > InstanceofExpression: > : RelationalExpression `instanceof` ReferenceType > : RelationalExpression `instanceof` PrimaryPattern <-- Note! Yes, ?PrimaryPattern?. > Some consequences: > > p1 & p2 & p3 && g1 && g2 parses as ((p1 & p2) & p3) && (g1 && g2), yay! > > p1 && g1 & p2 && g2 needs to be bracketed as (p1 && g1) & (p2 && g2) to parse > properly. But that's okay, as I think the second is much clearer. Or as `(p1 && g1) & p2 && g2`, but that?s still clearer than `p1 && g1 & p2 && g2`. > Let me know what you think. > > Gavin From john.r.rose at oracle.com Wed Mar 10 21:59:32 2021 From: john.r.rose at oracle.com (John Rose) Date: Wed, 10 Mar 2021 13:59:32 -0800 Subject: Guards redux In-Reply-To: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> Message-ID: <31091AAE-D117-4610-BE43-E26BB71972EE@oracle.com> Good. PrimaryPattern is a good concept. On Mar 10, 2021, at 6:47 AM, Gavin Bierman wrote: > > Guard: > : AssignmentExpression I think it should be this instead: Guard: : ConditionalAndExpression The effects of this narrower definition of a guard expression are two: First, any guard of the form (a || b), (a ? b : c), or (a = b) will require parentheses. Second, as a result, the following puzzlers will not be legal inputs: case P && a || b: // compare x instanceof P && a || b case P && a ? b : c: // compare x instanceof P && a ? b : c case P && a = b: // compare x instanceof P && a = b In all of these puzzlers, the ?extremely fortuitous? correspondence between the syntaxes of guarded cases and instanceof?s with following boolean logic are broken. The fix to align the meanings of the cases with the instanceof?s is to add parentheses: case P && (a || b): // compare x instanceof P && (a || b) case P && (a ? b : c): // compare x instanceof P && (a ? b : c) case P && (a = b): // compare x instanceof P && (a = b) Using the modified grammar rule above forces the coder to write the parentheses, I think. I think we should aim for ?perfectly fortuitous? here, not just ?well, look how often the syntaxes seem to mean the same thing although sometimes they don?t?. Indeed, this is my main reason for plumping for P && g in the first place. ? John From john.r.rose at oracle.com Wed Mar 10 22:05:09 2021 From: john.r.rose at oracle.com (John Rose) Date: Wed, 10 Mar 2021 14:05:09 -0800 Subject: Guards redux In-Reply-To: <637699712.2107687.1615393983786.JavaMail.zimbra@u-pem.fr> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <637699712.2107687.1615393983786.JavaMail.zimbra@u-pem.fr> Message-ID: <153BE596-1D29-4AA6-A454-0D2A6C4CC0CD@oracle.com> On Mar 10, 2021, at 8:33 AM, Remi Forax wrote: > > As i said to Gavin, i'm not at ease with using the symbol '&' in between patterns. Aren?t patterns inherently sequential and short-circuiting? Or am I having a failure of imagination? All of the pattern syntaxes mimic expression syntaxes, but expressions are (generally) non-short-circuiting, entailing unconditional evaluation of subexpressions. But every subpattern of a pattern is conditional, which is different from expressions. Thus, & in pattern-land is short-circuiting. (What would a non-short-circuiting pattern look like?) From john.r.rose at oracle.com Wed Mar 10 22:15:58 2021 From: john.r.rose at oracle.com (John Rose) Date: Wed, 10 Mar 2021 14:15:58 -0800 Subject: [External] : Re: Guards redux In-Reply-To: <8FAF4FB1-9070-49E9-88B8-F16A9163796E@oracle.com> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <637699712.2107687.1615393983786.JavaMail.zimbra@u-pem.fr> <8FAF4FB1-9070-49E9-88B8-F16A9163796E@oracle.com> Message-ID: <0BA27139-215A-4234-9F0B-4EE8934448DC@oracle.com> On Mar 10, 2021, at 11:10 AM, Guy Steele wrote: > > But in principle it does matter for patterns, because while patterns arguably do not involve _evaluation_, they most certainly involve _execution_ of possibly user-written code. If side effects can occur, the distinction arguably matters, and this is worth recognizing as we debate the design. I don?t think user-written patterns can reliably exclude side effects, and I think in some cases they will be useful (to accumulate some sort of ?log? on the side of matching activity, perhaps to support backtracking). So let?s say that one way or another we have to specify ordering of side effects patterns. (Eww: What about class loading side effects for resolved class names? Do we have to package type names in thunks for the pattern runtime? Please, no.) Given nested patterns P(Q,R), if P fails then there?s no way to execute Q. If Q fails there?s no benefit to executing R, although that would be possible to more closely emulate unconditional evaluation of function parameters. Is there any reason to do this? I think not. And then P(Q & R) also has no reason to execute R if Q fails. As I said to Remi, importing unconditional evaluation rules from expressions into patterns makes no sense. As Brian said, pattern-& is as similar to type-& to expression-&. From john.r.rose at oracle.com Wed Mar 10 22:19:47 2021 From: john.r.rose at oracle.com (John Rose) Date: Wed, 10 Mar 2021 14:19:47 -0800 Subject: Guards redux In-Reply-To: <7161794F-BBBA-4243-89AD-7C0F8DCC1766@oracle.com> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <7161794F-BBBA-4243-89AD-7C0F8DCC1766@oracle.com> Message-ID: <6FDF7204-9172-43E4-B065-A9747773D59D@oracle.com> On Mar 10, 2021, at 11:29 AM, Guy Steele wrote: > > Let me suggest this alternative: > > Pattern: > : ConditionalOrPattern > : ConditionalAndPattern `&&` Guard Clever. Too bad we can?t do this for expressions too. >> Guard: >> : AssignmentExpression > > Yes, ?AssignmentExpression? is the right thing here. Please see my previous objection: I think the looseness of AssignmentExpression leads to even worse puzzlers than the ones you are addressing above. From guy.steele at oracle.com Thu Mar 11 02:17:40 2021 From: guy.steele at oracle.com (Guy Steele) Date: Wed, 10 Mar 2021 21:17:40 -0500 Subject: Guards redux In-Reply-To: <6FDF7204-9172-43E4-B065-A9747773D59D@oracle.com> References: <6FDF7204-9172-43E4-B065-A9747773D59D@oracle.com> Message-ID: > On Mar 10, 2021, at 5:19 PM, John Rose wrote: > > ? On Mar 10, 2021, at 11:29 AM, Guy Steele wrote: >> >> Let me suggest this alternative: >> >> Pattern: >> : ConditionalOrPattern >> : ConditionalAndPattern `&&` Guard > > Clever. Too bad we can?t do this for expressions too. > >>> Guard: >>> : AssignmentExpression >> >> Yes, ?AssignmentExpression? is the right thing here. Duh. > Please see my previous objection: I think the looseness > of AssignmentExpression leads to even worse puzzlers > than the ones you are addressing above. > Actually, we had essentially the same idea for improving the grammar, but each of us spotted just one of the two problems. (Are there more?) You put it best: we should avoid uselessly enabling more puzzlers. From gavin.bierman at oracle.com Thu Mar 11 13:51:18 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Thu, 11 Mar 2021 13:51:18 +0000 Subject: Guards redux In-Reply-To: <31091AAE-D117-4610-BE43-E26BB71972EE@oracle.com> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <31091AAE-D117-4610-BE43-E26BB71972EE@oracle.com> Message-ID: <7E652ABA-6DF0-4CA2-B795-603CDAEF733F@oracle.com> Very clever, John. Yes, let?s not add any more puzzlers to the language! On 10 Mar 2021, at 21:59, John Rose > wrote: Good. PrimaryPattern is a good concept. On Mar 10, 2021, at 6:47 AM, Gavin Bierman > wrote: Guard: : AssignmentExpression I think it should be this instead: Guard: : ConditionalAndExpression The effects of this narrower definition of a guard expression are two: First, any guard of the form (a || b), (a ? b : c), or (a = b) will require parentheses. Second, as a result, the following puzzlers will not be legal inputs: case P && a || b: // compare x instanceof P && a || b case P && a ? b : c: // compare x instanceof P && a ? b : c case P && a = b: // compare x instanceof P && a = b In all of these puzzlers, the ?extremely fortuitous? correspondence between the syntaxes of guarded cases and instanceof?s with following boolean logic are broken. The fix to align the meanings of the cases with the instanceof?s is to add parentheses: case P && (a || b): // compare x instanceof P && (a || b) case P && (a ? b : c): // compare x instanceof P && (a ? b : c) case P && (a = b): // compare x instanceof P && (a = b) Using the modified grammar rule above forces the coder to write the parentheses, I think. I think we should aim for ?perfectly fortuitous? here, not just ?well, look how often the syntaxes seem to mean the same thing although sometimes they don?t?. Indeed, this is my main reason for plumping for P && g in the first place. ? John From gavin.bierman at oracle.com Fri Mar 12 10:11:46 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Fri, 12 Mar 2021 10:11:46 +0000 Subject: Guards redux In-Reply-To: <7E652ABA-6DF0-4CA2-B795-603CDAEF733F@oracle.com> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <31091AAE-D117-4610-BE43-E26BB71972EE@oracle.com> <7E652ABA-6DF0-4CA2-B795-603CDAEF733F@oracle.com> Message-ID: <55F888B1-062A-4C70-995C-3D1A16294B9C@oracle.com> Thanks everyone for the discussion. I have updated the JEP: http://openjdk.java.net/jeps/8213076 It proposes *guarded patterns* of the form `p&&e`, as well as parenthesized patterns, written `(p)`. I have left out AND and OR patterns, at least for now. Thanks to Guy we now know how to add them elegantly to the grammar when the time comes :-) When people come to play with this, I?d be especially interested to hear about the need for AND and OR patterns. (I have a feeling that OR is more important, but that?s another email...) Comments on the JEP very welcome. Thanks, Gavin > On 11 Mar 2021, at 13:51, Gavin Bierman wrote: > > Very clever, John. Yes, let?s not add any more puzzlers to the language! > >> On 10 Mar 2021, at 21:59, John Rose wrote: >> >> Good. PrimaryPattern is a good concept. >> >> On Mar 10, 2021, at 6:47 AM, Gavin Bierman wrote: >>> >>> Guard: >>> : AssignmentExpression >> >> I think it should be this instead: >> >> Guard: >> : ConditionalAndExpression >> >> The effects of this narrower definition of a guard expression >> are two: >> >> First, any guard of the form (a || b), (a ? b : c), or (a = b) >> will require parentheses. >> >> Second, as a result, the following puzzlers will not be legal >> inputs: >> >> case P && a || b: // compare x instanceof P && a || b >> case P && a ? b : c: // compare x instanceof P && a ? b : c >> case P && a = b: // compare x instanceof P && a = b >> >> In all of these puzzlers, the ?extremely fortuitous? >> correspondence between the syntaxes of guarded >> cases and instanceof?s with following boolean logic >> are broken. >> >> The fix to align the meanings of the cases with the >> instanceof?s is to add parentheses: >> >> case P && (a || b): // compare x instanceof P && (a || b) >> case P && (a ? b : c): // compare x instanceof P && (a ? b : c) >> case P && (a = b): // compare x instanceof P && (a = b) >> >> Using the modified grammar rule above forces the >> coder to write the parentheses, I think. >> >> I think we should aim for ?perfectly fortuitous? >> here, not just ?well, look how often the syntaxes >> seem to mean the same thing although sometimes >> they don?t?. Indeed, this is my main reason for >> plumping for P && g in the first place. >> >> ? John >> >> > From forax at univ-mlv.fr Fri Mar 12 11:02:18 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 12 Mar 2021 12:02:18 +0100 (CET) Subject: Guards redux In-Reply-To: <55F888B1-062A-4C70-995C-3D1A16294B9C@oracle.com> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <31091AAE-D117-4610-BE43-E26BB71972EE@oracle.com> <7E652ABA-6DF0-4CA2-B795-603CDAEF733F@oracle.com> <55F888B1-062A-4C70-995C-3D1A16294B9C@oracle.com> Message-ID: <1502213779.995099.1615546938812.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Gavin Bierman" > ?: "John Rose" > Cc: "amber-spec-experts" > Envoy?: Vendredi 12 Mars 2021 11:11:46 > Objet: Re: Guards redux > Thanks everyone for the discussion. I have updated the JEP: > > http://openjdk.java.net/jeps/8213076 > > It proposes *guarded patterns* of the form `p&&e`, as well as parenthesized > patterns, written `(p)`. Hi Gavin, nice, i find this version more appealing. In the first example that shows && as a guard, the example uses parenthesis which is my opinion should be removed case Triangle t && (t.calculateArea() > 100) -> System.out.println("Large Triangle"); if i read the grammar correctly, those parenthesis around the boolean expression are unnecessary case Triangle t && t.calculateArea() > 100 -> it will be more inline with the example above in the text that is using 'when' case Triangle t when t.calculateArea() > 100 -> Otherwise, in the document, the words "->-style" and ":-style" are hard to parse, i think array-style and colon-style are better terms. In the section "Dominance of pattern label", It's not clear to me if there is a dominance in between expressions, by example, can we have the following cases in that order ? case Foo(var a, var b) && a == 3: case Foo(var a, var b) && a == 3 && b == 4: for me the answer is yes, the dominance is computed on patterns only, not on expressions. > > I have left out AND and OR patterns, at least for now. Thanks to Guy we now know > how to add them elegantly to the grammar when the time comes :-) When people > come to play with this, I?d be especially interested to hear about the need for > AND and OR patterns. (I have a feeling that OR is more important, but that?s > another email...) > > Comments on the JEP very welcome. > > Thanks, > Gavin regards, R?mi > > >> On 11 Mar 2021, at 13:51, Gavin Bierman wrote: >> >> Very clever, John. Yes, let?s not add any more puzzlers to the language! >> >>> On 10 Mar 2021, at 21:59, John Rose wrote: >>> >>> Good. PrimaryPattern is a good concept. >>> >>> On Mar 10, 2021, at 6:47 AM, Gavin Bierman wrote: >>>> >>>> Guard: >>>> : AssignmentExpression >>> >>> I think it should be this instead: >>> >>> Guard: >>> : ConditionalAndExpression >>> >>> The effects of this narrower definition of a guard expression >>> are two: >>> >>> First, any guard of the form (a || b), (a ? b : c), or (a = b) >>> will require parentheses. >>> >>> Second, as a result, the following puzzlers will not be legal >>> inputs: >>> >>> case P && a || b: // compare x instanceof P && a || b >>> case P && a ? b : c: // compare x instanceof P && a ? b : c >>> case P && a = b: // compare x instanceof P && a = b >>> >>> In all of these puzzlers, the ?extremely fortuitous? >>> correspondence between the syntaxes of guarded >>> cases and instanceof?s with following boolean logic >>> are broken. >>> >>> The fix to align the meanings of the cases with the >>> instanceof?s is to add parentheses: >>> >>> case P && (a || b): // compare x instanceof P && (a || b) >>> case P && (a ? b : c): // compare x instanceof P && (a ? b : c) >>> case P && (a = b): // compare x instanceof P && (a = b) >>> >>> Using the modified grammar rule above forces the >>> coder to write the parentheses, I think. >>> >>> I think we should aim for ?perfectly fortuitous? >>> here, not just ?well, look how often the syntaxes >>> seem to mean the same thing although sometimes >>> they don?t?. Indeed, this is my main reason for >>> plumping for P && g in the first place. >>> >>> ? John >>> >>> From gavin.bierman at oracle.com Fri Mar 12 11:36:19 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Fri, 12 Mar 2021 11:36:19 +0000 Subject: [External] : Re: Guards redux In-Reply-To: <1502213779.995099.1615546938812.JavaMail.zimbra@u-pem.fr> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <31091AAE-D117-4610-BE43-E26BB71972EE@oracle.com> <7E652ABA-6DF0-4CA2-B795-603CDAEF733F@oracle.com> <55F888B1-062A-4C70-995C-3D1A16294B9C@oracle.com> <1502213779.995099.1615546938812.JavaMail.zimbra@u-pem.fr> Message-ID: <13388A47-D180-47D8-80B2-4F58D15433FC@oracle.com> > On 12 Mar 2021, at 11:02, Remi Forax wrote: > > ----- Mail original ----- >> De: "Gavin Bierman" >> ?: "John Rose" >> Cc: "amber-spec-experts" >> Envoy?: Vendredi 12 Mars 2021 11:11:46 >> Objet: Re: Guards redux > >> Thanks everyone for the discussion. I have updated the JEP: >> >> http://openjdk.java.net/jeps/8213076 >> >> It proposes *guarded patterns* of the form `p&&e`, as well as parenthesized >> patterns, written `(p)`. > > > Hi Gavin, > nice, i find this version more appealing. Yay! > In the first example that shows && as a guard, the example uses parenthesis which is my opinion should be removed > case Triangle t && (t.calculateArea() > 100) -> > System.out.println("Large Triangle"); > > if i read the grammar correctly, those parenthesis around the boolean expression are unnecessary > case Triangle t && t.calculateArea() > 100 -> They are unnecessary, but this example comes before the grammar, so I didn?t want anyone to get confused. > > it will be more inline with the example above in the text that is using 'when' > case Triangle t when t.calculateArea() > 100 -> > > > Otherwise, in the document, the words "->-style" and ":-style" are hard to parse, i think array-style and colon-style are better terms. I?ll take a look. > In the section "Dominance of pattern label", > It's not clear to me if there is a dominance in between expressions, > by example, can we have the following cases in that order ? > > case Foo(var a, var b) && a == 3: > case Foo(var a, var b) && a == 3 && b == 4: > > for me the answer is yes, the dominance is computed on patterns only, not on expressions. I agree. I don?t think trying to calculate dominance between expressions is viable. Thanks R?mi! Gavin From brian.goetz at oracle.com Fri Mar 12 13:30:57 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 12 Mar 2021 08:30:57 -0500 Subject: Guards redux In-Reply-To: <1502213779.995099.1615546938812.JavaMail.zimbra@u-pem.fr> References: <7EB71CD0-E0F7-4FCD-87EA-5AEEE3643D15@oracle.com> <31091AAE-D117-4610-BE43-E26BB71972EE@oracle.com> <7E652ABA-6DF0-4CA2-B795-603CDAEF733F@oracle.com> <55F888B1-062A-4C70-995C-3D1A16294B9C@oracle.com> <1502213779.995099.1615546938812.JavaMail.zimbra@u-pem.fr> Message-ID: > In the section "Dominance of pattern label", > It's not clear to me if there is a dominance in between expressions, > by example, can we have the following cases in that order ? > > case Foo(var a, var b) && a == 3: > case Foo(var a, var b) && a == 3 && b == 4: > > for me the answer is yes, the dominance is computed on patterns only, not on expressions. This problem is related to totality; could we determine that ??? case Foo(int x) && x > 0: ??? case Foo(int x) && x <= 0: is total?? For this case, sure, but in general, at best we can do a bad job, and spend more and more effort to get closer, but never quite getting there.? Computing dominance between expressions is the same problem, with the same sad result. So, I think the answer is: when you go off into expression world, we lose the ability to reason about dominance and totality.? We know that P dominates P&g for any g, but that's about as far as we go. From brian.goetz at oracle.com Fri Mar 12 14:12:21 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 12 Mar 2021 09:12:21 -0500 Subject: Switch labels (null again), some tweaking Message-ID: The JEP has some examples of how the `null` case label can combine with others.? But I would like to propose a more general way to describe what's going on. This doesn't change the proposed language (much), as much as describing/specifying it in a more general way. We have the following kinds of switch labels: ??? case ??? case null ??? case ??? default The question is, which can be combined with each other into a single case, such as: ??? case 3, null, 5: This question is related to, which can fall into each other: ??? case 3: ??? case null: ??? case 5: We can say that certain labels are compatible with certain others, and ones that are compatible can be combined / are candidates for fallthrough, by defining a compatibility predicate: ?- All case labels are compatible with each other; ?- The `null` label is compatible with labels; ?- The `null` label is compatible with `default`; ?- The `null` label is compatible with (explicit) type patterns: (There is already a check that each label is applicable to the type of the target.) Then we say: you can combine N case labels as long as they are all compatible with each other.? Combination includes both comma-separated lists in one case, as well as when one case is _reachable_ from another (fall through.)? And the two can be combined: ??? case 3, 4: // fall through ??? case null: So the following are allowed: ??? case 1, 2, 3, null: ??? case null, 1, 2, 3: ??? case null, Object o: ??? case Object o, null: ??? case null, default:??? // special syntax rule for combining default The following special rules apply: ?- `default` can be used as a case label when combined with compatible case labels (see last example above); ?- When `null` combines with a type pattern, the binding variable of the type pattern can bind null. The semantics outlined in Gavin's JEP are unchanged; this is just a new and less fussy way to describe the behavior of the null label / specify the interaction with fallthrough. From brian.goetz at oracle.com Fri Mar 12 19:58:40 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 12 Mar 2021 14:58:40 -0500 Subject: Looking ahead: pattern assignment Message-ID: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> While this is not on the immediate horizon, I think we are ready to put the pieces together for pattern assignment.? I think we now understand the form this has to take, and the constraints around it. Just as we were successfully able to unify pattern variables with locals*, I would like to be able to unify pattern assignment with assignment. A pattern assignment takes the form: ??? P = e where P is a pattern with at least one binding variable that is total (perhaps with remainder) on the type of e.? (If P has some remainder on the type of e, then the assignment will throw NPE or ICCE.)? All bindings in P are in scope and DA for the remainder of the block in which P appears, just as with local variable declaration. Pattern assignment should work in all of the following contexts: ?- Assignment statements: P = e ?- foreach-loops: for (P : e) { ... } ?- (optional) try-with-resources: try (P = e) { ... } ?- (optional) method formals: void m(Point(var x, var y) p) { ... } ?- (optional) lambda formals: (Point(var x, var y) p) -> { ... } (And I'm sure I forgot some.) Minimally, we have to align the semantics of local variable declaration with assignment with that of pattern matching; `T t = e` should have the same semantics whether we view it as a local declaration plus assignment, or a pattern match.? This means that we have to, minimally, align the assignment-context conversions in JLS 5.? (If we wish to support patterns in method/lambda formals, we also have to align the method-invocation context conversions.) Early in the game, we explored supporting partial patterns in pattern assignment, such as: ??? let P = e ??? else { ... } where the `else` clause must either complete abruptly, or assign to all bindings declared in `P`.? (It wasn't until we unified pattern variables with locals that there was an obvious way to specify the latter.)? While this construct is sound, it is in tension with other uses of pattern assignment: ?- (syntactic) Its pretty hard to imagine an `else` clause without introducing the assignment with some sort of keyword, such as `let`, but this limits its usefulness in other contexts such as method parameter declarations; ?- (pragmatic) It just doesn't add very much value; if the else throws, it is no less verbose than an if-else. The remaining case where this construct helps is when we want to assign default values: ??? let Point(var x, var y) = aPoint ??? else { x = y = 0; } ??? // can use x, y here either way But, I think we can get there another way, by letting patterns bind to existing variables somehow (we want something like this for the analogue of super-delegation and similar in pattern declarations anyway.)? I won't paint that bikeshed here, except to suggest that the let-else construct seems to be a losing price-performance proposition. I suspect the right time to formalize pattern assignment is when we formalize deconstructor declarations (probably next round). In the meantime, we should: ?- gather a complete list of contexts where pattern assignment makes sense; ?- nail down semantics of primitive type patterns (see earlier mail); ?- think about how to align the conversion rules in JLS 5 to align with existing usage. *the only remaining difference between pattern variables and locals is that pattern variables have a more interestingly-shaped scope (and perhaps in the future, pattern variables may have multiple declaration points in the presence of OR patterns / merging via ORing of boolean expressions) From forax at univ-mlv.fr Fri Mar 12 21:35:59 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 12 Mar 2021 22:35:59 +0100 (CET) Subject: Looking ahead: pattern assignment In-Reply-To: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> References: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> Message-ID: <1999530970.1341574.1615584959429.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "amber-spec-experts" > Envoy?: Vendredi 12 Mars 2021 20:58:40 > Objet: Looking ahead: pattern assignment > While this is not on the immediate horizon, I think we are ready to put the > pieces together for pattern assignment. I think we now understand the form this > has to take, and the constraints around it. > Just as we were successfully able to unify pattern variables with locals*, I > would like to be able to unify pattern assignment with assignment. > A pattern assignment takes the form: > P = e > where P is a pattern with at least one binding variable that is total (perhaps > with remainder) on the type of e. (If P has some remainder on the type of e, > then the assignment will throw NPE or ICCE.) All bindings in P are in scope and > DA for the remainder of the block in which P appears, just as with local > variable declaration. yes, with remainder please :) NPE if the expression is null, ICCE if the number/type of the bindings are incompatible. > Pattern assignment should work in all of the following contexts: > - Assignment statements: P = e > - foreach-loops: for (P : e) { ... } > - (optional) try-with-resources: try (P = e) { ... } > - (optional) method formals: void m(Point(var x, var y) p) { ... } > - (optional) lambda formals: (Point(var x, var y) p) -> { ... } > (And I'm sure I forgot some.) > Minimally, we have to align the semantics of local variable declaration with > assignment with that of pattern matching; `T t = e` should have the same > semantics whether we view it as a local declaration plus assignment, or a > pattern match. This means that we have to, minimally, align the > assignment-context conversions in JLS 5. (If we wish to support patterns in > method/lambda formals, we also have to align the method-invocation context > conversions.) More questions, About the conversions, you means the conversions with the top-level type of the pattern, or conversions with the sub-patterns too ? For lambda formals, there is a syntax question, do we support a syntax without parenthesis if there are only one argument ? Point(var x, var y) -> ... For methods and lambda formals, does the top-level pattern as to have a binding ? Like in your examples void m(Point(var x, var y) p) { ... } or can we avoid it void m(Point(var x, var y)) { ... } For classical assignment, enhanced for loop, try-with-resources and lambda formals we can have inference, do we have inference with patterns ? - assignment (var x, var y) = aPoint; - enhanced loop for((var x, var y) : aListOfPoints) { ... } - try-with-resources try((var x, var y): aClosablePoint) { ... } - lambdas formals Consumer consumer = ((var x, var y)) -> ... Also should we support the parenthesized pattern and the AND pattern in those contexts ? - the parenthesized pattern may makes the grammar ambiguous. - do we allow something like Point(var x, _) & Point(_, var y) = aPoint; [...] > I suspect the right time to formalize pattern assignment is when we formalize > deconstructor declarations (probably next round). In the meantime, we should: > - gather a complete list of contexts where pattern assignment makes sense; > - nail down semantics of primitive type patterns (see earlier mail); > - think about how to align the conversion rules in JLS 5 to align with existing > usage. > *the only remaining difference between pattern variables and locals is that > pattern variables have a more interestingly-shaped scope (and perhaps in the > future, pattern variables may have multiple declaration points in the presence > of OR patterns / merging via ORing of boolean expressions) R?mi From amalloy at google.com Fri Mar 12 21:58:44 2021 From: amalloy at google.com (Alan Malloy) Date: Fri, 12 Mar 2021 13:58:44 -0800 Subject: Looking ahead: pattern assignment In-Reply-To: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> References: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> Message-ID: try-with-resources is a little more subtle than the other contexts. Suppose that I write: try (Foo(Bar x)) { ... } What should be closed in the finally? The variable x that we bound to, or the Foo that contained it? Both answers seem a little weird to me. Might it be better not to allow patterns in this context? On Fri, Mar 12, 2021 at 12:41 PM Brian Goetz wrote: > While this is not on the immediate horizon, I think we are ready to put > the pieces together for pattern assignment. I think we now understand the > form this has to take, and the constraints around it. > > Just as we were successfully able to unify pattern variables with locals*, > I would like to be able to unify pattern assignment with assignment. > > A pattern assignment takes the form: > > P = e > > where P is a pattern with at least one binding variable that is total > (perhaps with remainder) on the type of e. (If P has some remainder on the > type of e, then the assignment will throw NPE or ICCE.) All bindings in P > are in scope and DA for the remainder of the block in which P appears, just > as with local variable declaration. > > Pattern assignment should work in all of the following contexts: > > - Assignment statements: P = e > - foreach-loops: for (P : e) { ... } > - (optional) try-with-resources: try (P = e) { ... } > - (optional) method formals: void m(Point(var x, var y) p) { ... } > - (optional) lambda formals: (Point(var x, var y) p) -> { ... } > > (And I'm sure I forgot some.) > > Minimally, we have to align the semantics of local variable declaration > with assignment with that of pattern matching; `T t = e` should have the > same semantics whether we view it as a local declaration plus assignment, > or a pattern match. This means that we have to, minimally, align the > assignment-context conversions in JLS 5. (If we wish to support patterns > in method/lambda formals, we also have to align the method-invocation > context conversions.) > > Early in the game, we explored supporting partial patterns in pattern > assignment, such as: > > let P = e > else { ... } > > where the `else` clause must either complete abruptly, or assign to all > bindings declared in `P`. (It wasn't until we unified pattern variables > with locals that there was an obvious way to specify the latter.) While > this construct is sound, it is in tension with other uses of pattern > assignment: > > - (syntactic) Its pretty hard to imagine an `else` clause without > introducing the assignment with some sort of keyword, such as `let`, but > this limits its usefulness in other contexts such as method parameter > declarations; > - (pragmatic) It just doesn't add very much value; if the else throws, it > is no less verbose than an if-else. > > The remaining case where this construct helps is when we want to assign > default values: > > let Point(var x, var y) = aPoint > else { x = y = 0; } > // can use x, y here either way > > But, I think we can get there another way, by letting patterns bind to > existing variables somehow (we want something like this for the analogue of > super-delegation and similar in pattern declarations anyway.) I won't > paint that bikeshed here, except to suggest that the let-else construct > seems to be a losing price-performance proposition. > > I suspect the right time to formalize pattern assignment is when we > formalize deconstructor declarations (probably next round). In the > meantime, we should: > > - gather a complete list of contexts where pattern assignment makes sense; > - nail down semantics of primitive type patterns (see earlier mail); > - think about how to align the conversion rules in JLS 5 to align with > existing usage. > > > > > *the only remaining difference between pattern variables and locals is > that pattern variables have a more interestingly-shaped scope (and perhaps > in the future, pattern variables may have multiple declaration points in > the presence of OR patterns / merging via ORing of boolean expressions) > From forax at univ-mlv.fr Fri Mar 12 22:14:42 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 12 Mar 2021 23:14:42 +0100 (CET) Subject: Looking ahead: pattern assignment In-Reply-To: References: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> Message-ID: <409002784.1343895.1615587282942.JavaMail.zimbra@u-pem.fr> > De: "Alan Malloy" > ?: "Brian Goetz" > Cc: "amber-spec-experts" > Envoy?: Vendredi 12 Mars 2021 22:58:44 > Objet: Re: Looking ahead: pattern assignment > try-with-resources is a little more subtle than the other contexts. Suppose that > I write: > try (Foo(Bar x) = ...) { > ... > } > What should be closed in the finally? The variable x that we bound to, or the > Foo that contained it? The instance of Foo, not the instance of Bar. > Both answers seem a little weird to me. Might it be better not to allow patterns > in this context ? It can be handy if we have a pattern method that decomposes the Closeable, by example we have a pattern method HttpRequest.parse() that returns the header and the content of the HTTP request try (HttpRequest.parse(var header, var content) = getAHttpRequestFrom(...)) { ... } regards, R?mi > On Fri, Mar 12, 2021 at 12:41 PM Brian Goetz < [ mailto:brian.goetz at oracle.com | > brian.goetz at oracle.com ] > wrote: >> While this is not on the immediate horizon, I think we are ready to put the >> pieces together for pattern assignment. I think we now understand the form this >> has to take, and the constraints around it. >> Just as we were successfully able to unify pattern variables with locals*, I >> would like to be able to unify pattern assignment with assignment. >> A pattern assignment takes the form: >> P = e >> where P is a pattern with at least one binding variable that is total (perhaps >> with remainder) on the type of e. (If P has some remainder on the type of e, >> then the assignment will throw NPE or ICCE.) All bindings in P are in scope and >> DA for the remainder of the block in which P appears, just as with local >> variable declaration. >> Pattern assignment should work in all of the following contexts: >> - Assignment statements: P = e >> - foreach-loops: for (P : e) { ... } >> - (optional) try-with-resources: try (P = e) { ... } >> - (optional) method formals: void m(Point(var x, var y) p) { ... } >> - (optional) lambda formals: (Point(var x, var y) p) -> { ... } >> (And I'm sure I forgot some.) >> Minimally, we have to align the semantics of local variable declaration with >> assignment with that of pattern matching; `T t = e` should have the same >> semantics whether we view it as a local declaration plus assignment, or a >> pattern match. This means that we have to, minimally, align the >> assignment-context conversions in JLS 5. (If we wish to support patterns in >> method/lambda formals, we also have to align the method-invocation context >> conversions.) >> Early in the game, we explored supporting partial patterns in pattern >> assignment, such as: >> let P = e >> else { ... } >> where the `else` clause must either complete abruptly, or assign to all bindings >> declared in `P`. (It wasn't until we unified pattern variables with locals that >> there was an obvious way to specify the latter.) While this construct is sound, >> it is in tension with other uses of pattern assignment: >> - (syntactic) Its pretty hard to imagine an `else` clause without introducing >> the assignment with some sort of keyword, such as `let`, but this limits its >> usefulness in other contexts such as method parameter declarations; >> - (pragmatic) It just doesn't add very much value; if the else throws, it is no >> less verbose than an if-else. >> The remaining case where this construct helps is when we want to assign default >> values: >> let Point(var x, var y) = aPoint >> else { x = y = 0; } >> // can use x, y here either way >> But, I think we can get there another way, by letting patterns bind to existing >> variables somehow (we want something like this for the analogue of >> super-delegation and similar in pattern declarations anyway.) I won't paint >> that bikeshed here, except to suggest that the let-else construct seems to be a >> losing price-performance proposition. >> I suspect the right time to formalize pattern assignment is when we formalize >> deconstructor declarations (probably next round). In the meantime, we should: >> - gather a complete list of contexts where pattern assignment makes sense; >> - nail down semantics of primitive type patterns (see earlier mail); >> - think about how to align the conversion rules in JLS 5 to align with existing >> usage. >> *the only remaining difference between pattern variables and locals is that >> pattern variables have a more interestingly-shaped scope (and perhaps in the >> future, pattern variables may have multiple declaration points in the presence >> of OR patterns / merging via ORing of boolean expressions) From brian.goetz at oracle.com Sat Mar 13 01:41:58 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 12 Mar 2021 20:41:58 -0500 Subject: [External] : Re: Looking ahead: pattern assignment In-Reply-To: <1999530970.1341574.1615584959429.JavaMail.zimbra@u-pem.fr> References: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> <1999530970.1341574.1615584959429.JavaMail.zimbra@u-pem.fr> Message-ID: <1fb9d7a0-af7b-05c5-531e-691b951c8520@oracle.com> > Minimally, we have to align the semantics of local variable > declaration with assignment with that of pattern matching; `T t = > e` should have the same semantics whether we view it as a local > declaration plus assignment, or a pattern match. This means that > we have to, minimally, align the assignment-context conversions in > JLS 5.? (If we wish to support patterns in method/lambda formals, > we also have to align the method-invocation context conversions.) > > > More questions, > About the conversions, you means the conversions with the top-level > type of the pattern, or conversions with the sub-patterns too ? What I mean is that if I have a method ???? void m(Integer x) { } and I call m(1), then today we do an boxing conversion because boxing is one of the conversions we do in method invocation context.? If we want to interpret this as a pattern match, we should get the same answer, which means we need to define what conversions we apply to the parameter when the parameter pattern has a certain target type, and that has to align with existing invocation context conversions. > For lambda formals, there is a syntax question, do we support a syntax > without parenthesis if there are only one argument ? > ? Point(var x, var y) -> ... No for now; ask me again in two years ;) > For methods and lambda formals, does the top-level pattern as to have > a binding ? > Like in your examples > ? void m(Point(var x, var y) p) { ... } > or can we avoid it > ? void m(Point(var x, var y)) { ... } Today, yes, because method parameters need names.? Tomorrow, when we allow method parameters to be elided via _, we might consider allowing omitting the parameter binding, but we might also just require it to be _. > For classical assignment, enhanced for loop, try-with-resources and > lambda formals we can have inference, do we have inference with patterns ? > ? - assignment > ??? (var x, var y) = aPoint; That's not a pattern :) ??? Point(var x, var y) = aPoint is a pattern.? In this case, we can infer what type x and y have based on the declaration of the selected deconstructor. > ? - enhanced loop > ??? for((var x, var y) : aListOfPoints) { ... } > ? - try-with-resources > ??? try((var x, var y): aClosablePoint) { ... } > ? - lambdas formals > ??? Consumer consumer = ((var x, var y)) -> ... Same "not a pattern" comment. > Also should we support the parenthesized pattern and the AND pattern > in those contexts ? The key criteria is totality.? In practicality that means unguarded type and deconstruction patterns. From brian.goetz at oracle.com Sat Mar 13 01:42:35 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 12 Mar 2021 20:42:35 -0500 Subject: [External] : Re: Looking ahead: pattern assignment In-Reply-To: References: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> Message-ID: <322b5f0e-34c8-9d0a-a2e2-ce09e3cd6770@oracle.com> It would have to be something like ??? try (Deconstructor(var x, var y) d = e) { } and then `d` would be the resource.? It might be that this comes up so rarely that we don't bother. On 3/12/2021 4:58 PM, Alan Malloy wrote: > try-with-resources is a little more subtle than the other contexts. > Suppose that I write: > > try (Foo(Bar x)) { > ? ... > } > > What should be closed in the finally? The variable x that we bound to, > or the Foo that contained it? Both answers seem a little weird to me. > Might it be better not to allow patterns in this context? > > On Fri, Mar 12, 2021 at 12:41 PM Brian Goetz > wrote: > > While this is not on the immediate horizon, I think we are ready > to put the pieces together for pattern assignment.? I think we now > understand the form this has to take, and the constraints around it. > > Just as we were successfully able to unify pattern variables with > locals*, I would like to be able to unify pattern assignment with > assignment. > > A pattern assignment takes the form: > > ??? P = e > > where P is a pattern with at least one binding variable that is > total (perhaps with remainder) on the type of e.? (If P has some > remainder on the type of e, then the assignment will throw NPE or > ICCE.)? All bindings in P are in scope and DA for the remainder of > the block in which P appears, just as with local variable declaration. > > Pattern assignment should work in all of the following contexts: > > ?- Assignment statements: P = e > ?- foreach-loops: for (P : e) { ... } > ?- (optional) try-with-resources: try (P = e) { ... } > ?- (optional) method formals: void m(Point(var x, var y) p) { ... } > ?- (optional) lambda formals: (Point(var x, var y) p) -> { ... } > > (And I'm sure I forgot some.) > > Minimally, we have to align the semantics of local variable > declaration with assignment with that of pattern matching; `T t = > e` should have the same semantics whether we view it as a local > declaration plus assignment, or a pattern match.? This means that > we have to, minimally, align the assignment-context conversions in > JLS 5.? (If we wish to support patterns in method/lambda formals, > we also have to align the method-invocation context conversions.) > > Early in the game, we explored supporting partial patterns in > pattern assignment, such as: > > ??? let P = e > ??? else { ... } > > where the `else` clause must either complete abruptly, or assign > to all bindings declared in `P`.? (It wasn't until we unified > pattern variables with locals that there was an obvious way to > specify the latter.)? While this construct is sound, it is in > tension with other uses of pattern assignment: > > ?- (syntactic) Its pretty hard to imagine an `else` clause without > introducing the assignment with some sort of keyword, such as > `let`, but this limits its usefulness in other contexts such as > method parameter declarations; > ?- (pragmatic) It just doesn't add very much value; if the else > throws, it is no less verbose than an if-else. > > The remaining case where this construct helps is when we want to > assign default values: > > ??? let Point(var x, var y) = aPoint > ??? else { x = y = 0; } > ??? // can use x, y here either way > > But, I think we can get there another way, by letting patterns > bind to existing variables somehow (we want something like this > for the analogue of super-delegation and similar in pattern > declarations anyway.)? I won't paint that bikeshed here, except to > suggest that the let-else construct seems to be a losing > price-performance proposition. > > I suspect the right time to formalize pattern assignment is when > we formalize deconstructor declarations (probably next round).? In > the meantime, we should: > > ?- gather a complete list of contexts where pattern assignment > makes sense; > ?- nail down semantics of primitive type patterns (see earlier mail); > ?- think about how to align the conversion rules in JLS 5 to align > with existing usage. > > > > > *the only remaining difference between pattern variables and > locals is that pattern variables have a more interestingly-shaped > scope (and perhaps in the future, pattern variables may have > multiple declaration points in the presence of OR patterns / > merging via ORing of boolean expressions) > From forax at univ-mlv.fr Sat Mar 13 11:31:33 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Sat, 13 Mar 2021 12:31:33 +0100 (CET) Subject: [External] : Re: Looking ahead: pattern assignment In-Reply-To: <1fb9d7a0-af7b-05c5-531e-691b951c8520@oracle.com> References: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> <1999530970.1341574.1615584959429.JavaMail.zimbra@u-pem.fr> <1fb9d7a0-af7b-05c5-531e-691b951c8520@oracle.com> Message-ID: <259950857.1436833.1615635093000.JavaMail.zimbra@u-pem.fr> De: "Brian Goetz" ?: "Remi Forax" Cc: "amber-spec-experts" Envoy?: Samedi 13 Mars 2021 02:41:58 Objet: Re: [External] : Re: Looking ahead: pattern assignment BQ_BEGIN BQ_BEGIN BQ_BEGIN Minimally, we have to align the semantics of local variable declaration with assignment with that of pattern matching; `T t = e` should have the same semantics whether we view it as a local declaration plus assignment, or a pattern match. This means that we have to, minimally, align the assignment-context conversions in JLS 5. (If we wish to support patterns in method/lambda formals, we also have to align the method-invocation context conversions.) BQ_END More questions, About the conversions, you means the conversions with the top-level type of the pattern, or conversions with the sub-patterns too ? BQ_END What I mean is that if I have a method void m(Integer x) { } and I call m(1), then today we do an boxing conversion because boxing is one of the conversions we do in method invocation context. If we want to interpret this as a pattern match, we should get the same answer, which means we need to define what conversions we apply to the parameter when the parameter pattern has a certain target type, and that has to align with existing invocation context conversions. BQ_END What is not clear to me is are you proposing those conversion to be applied on the target type of the pattern or even on the target type of the sub patterns. But perhaps it's a more general question for the deconstruction pattern, if we do not use 'var' case Point(Type1 x, Type2 y): ... what are the possible types for Type1 and Type2 given that Point is declared as Point(DeclaredType1 x, DeclaredType2 y). Do Type1 has to be DeclaredType1 (resp. Type2 == DeclaredType2) or do some conversions are allowed ? BQ_BEGIN BQ_BEGIN For lambda formals, there is a syntax question, do we support a syntax without parenthesis if there are only one argument ? Point(var x, var y) -> ... BQ_END No for now; ask me again in two years ;) BQ_BEGIN For methods and lambda formals, does the top-level pattern as to have a binding ? Like in your examples void m(Point(var x, var y) p) { ... } or can we avoid it void m(Point(var x, var y)) { ... } BQ_END Today, yes, because method parameters need names. Tomorrow, when we allow method parameters to be elided via _, we might consider allowing omitting the parameter binding, but we might also just require it to be _. BQ_END Given that you can get the parameter names by reflection, requiring '_' may not be a bad idea. This lead to another question, should we consider using a Pattern declared inside a method declaration only as syntactic sugar or should the notation reflected in an attribute of the method accessible by reflection ? BQ_BEGIN BQ_BEGIN For classical assignment, enhanced for loop, try-with-resources and lambda formals we can have inference, do we have inference with patterns ? - assignment (var x, var y) = aPoint; BQ_END That's not a pattern :) Point(var x, var y) = aPoint is a pattern. In this case, we can infer what type x and y have based on the declaration of the selected deconstructor. BQ_END It's an alternative syntax to the deconstruction pattern, usual deconstruction pattern is Type(pattern1, pattern2, ...) The alternative syntax is (pattern1, pattern2, ...) and the type is found by type inference. This may be important to explore that now because the syntax may clash with the parenthesized pattern because (pattern) can be seen as both. BQ_BEGIN BQ_BEGIN - enhanced loop for((var x, var y) : aListOfPoints) { ... } - try-with-resources try((var x, var y): aClosablePoint) { ... } - lambdas formals Consumer consumer = ((var x, var y)) -> ... BQ_END Same "not a pattern" comment. BQ_BEGIN Also should we support the parenthesized pattern and the AND pattern in those contexts ? BQ_END The key criteria is totality. In practicality that means unguarded type and deconstruction patterns. BQ_END And the pattern method if we allow some pattern methods to be declared as total. R?mi From brian.goetz at oracle.com Sun Mar 14 12:59:10 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Sun, 14 Mar 2021 08:59:10 -0400 Subject: [External] : Re: Looking ahead: pattern assignment In-Reply-To: <259950857.1436833.1615635093000.JavaMail.zimbra@u-pem.fr> References: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> <1999530970.1341574.1615584959429.JavaMail.zimbra@u-pem.fr> <1fb9d7a0-af7b-05c5-531e-691b951c8520@oracle.com> <259950857.1436833.1615635093000.JavaMail.zimbra@u-pem.fr> Message-ID: > What I mean is that if I have a method > > ???? void m(Integer x) { } > > and I call m(1), then today we do an boxing conversion because > boxing is one of the conversions we do in method invocation > context.? If we want to interpret this as a pattern match, we > should get the same answer, which means we need to define what > conversions we apply to the parameter when the parameter pattern > has a certain target type, and that has to align with existing > invocation context conversions. > > > What is not clear to me is are you proposing those conversion to be > applied on the target type of the pattern or even on the target type > of the sub patterns. The sub-patterns don't enter into it.? Because a pattern in this context must be total on the type of the parameter/assignee, if it is a pattern with sub-patterns, they must be total too, otherwise the whole thing is not total. If I have ??? Point(var x && x == 0, var y) = aPoint I'm going to get an error that says that the pattern on the LHS is not total on the target type of the thing on the RHS, which is Point. > But perhaps it's a more general question for the deconstruction > pattern, if we do not use 'var' > ? case Point(Type1 x, Type2 y): ... > what are the possible types for Type1 and Type2 given that Point is > declared as Point(DeclaredType1 x, DeclaredType2 y). > Do Type1 has to be DeclaredType1 (resp. Type2 == DeclaredType2) or do > some conversions are allowed ? Go back to the previous point: the pattern must be total, so deconstructor sub-patterns must be total on the type of the deconstructor bindings. I think you're asking something like: if we have deconstructor `Foo(int x)`, can I invoke it with `Foo(Integer x)`?? In other words, is there a set of _pattern conversions_ where we are willing to make up the difference between two similar-enough patterns, and call it total?? I have resisted this so far, because I think there are ample other ways to get to what you want without the complexity of JLS Ch5.? But I'll make a note to come back with a more detailed explanation of the problem here. > This lead to another question, should we consider using a Pattern > declared inside a method declaration only as syntactic sugar or should > the notation reflected in an attribute of the method accessible by > reflection ? Definitely sugar.? Suppose we have a pattern P with target type T: ??? void m(P x) { ???????? M ??? } is equivalent to ??? void m(T x) { ????????? P = x; ???????? M ??? } That said, its possible that _Javadoc_ might want to do more.? Since total patterns can still have remainder, its reasonable for the Javadoc to capture this.? But reflection is about what's in the classfile, not the language model, so this is 100% not reflection's business. > > It's an alternative syntax to the deconstruction pattern, usual > deconstruction pattern is > ? Type(pattern1, pattern2, ...) > The alternative syntax is > ? (pattern1, pattern2, ...) > and the type is found by type inference. OK, as long as we're clear that this "alternate syntax" just in your head.? (See also: https://en.wikipedia.org/wiki/Alternative_facts). From forax at univ-mlv.fr Mon Mar 15 11:08:11 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 15 Mar 2021 12:08:11 +0100 (CET) Subject: Type pattern and raw type Message-ID: <1493278740.392474.1615806491528.JavaMail.zimbra@u-pem.fr> Hi, currently when there is an instanceof + type pattern with a raw type, javec doesn't raise any warning (oops), worst it compiles because i believe it should not, but for that, the spec has to be amended. interface Foo { void set(T t); } class FooImpl implements Foo { private String value; @Override public void set(String s) { value = s; } } public static void main(String[] args) { Object o = new FooImpl(); if (o instanceof Foo foo) { foo.set(3); } } The JLS 14.30.2 says "An expression, e, that is not the null literal is compatible with a type test pattern of type T if e can be converted to type T by a casting conversion (5.5), and the casting conversion does not make use of a narrowing reference conversion which is unchecked (5.1.6.2)." Unchecked conversions are forbidden because of type pollution, that why you can not write if (o instanceof Foo foo) { Raw type conversions also leads to type pollution, so i think that a type pattern with a raw type should be forbidden too. So at least, javac should raise a warning and IMO the spec should be amended to fordid raw types too. regards, R?mi From brian.goetz at oracle.com Mon Mar 15 13:28:26 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 15 Mar 2021 09:28:26 -0400 Subject: Type pattern and raw type In-Reply-To: <1493278740.392474.1615806491528.JavaMail.zimbra@u-pem.fr> References: <1493278740.392474.1615806491528.JavaMail.zimbra@u-pem.fr> Message-ID: <1c4479df-1830-f952-1261-898e484cccc2@oracle.com> It's definitely a mistake that we do nothing here. There are two options.? The obvious is to simply raise a raw warning; this is a straightforward spec/impl fix which we can easily do. The other option is to do as we did with method refs, where we apply type inference to an unadorned `Foo` pattern.? But, if the user wanted type inference, they'd have said `var`.? So I think the right move is to issue the warning. On 3/15/2021 7:08 AM, Remi Forax wrote: > Hi, currently when there is an instanceof + type pattern with a raw type, > javec doesn't raise any warning (oops), worst it compiles because i believe it should not, but for that, the spec has to be amended. > > interface Foo { > void set(T t); > } > > class FooImpl implements Foo { > private String value; > > @Override > public void set(String s) { > value = s; > } > } > > public static void main(String[] args) { > Object o = new FooImpl(); > if (o instanceof Foo foo) { > foo.set(3); > } > } > > The JLS 14.30.2 says > "An expression, e, that is not the null literal is compatible with a type test pattern of type T if e can be converted to type T by a casting conversion (5.5), and the casting conversion does not make use of a narrowing reference conversion which is unchecked (5.1.6.2)." > > Unchecked conversions are forbidden because of type pollution, that why you can not write > if (o instanceof Foo foo) { > > Raw type conversions also leads to type pollution, so i think that a type pattern with a raw type should be forbidden too. > > So at least, javac should raise a warning and IMO the spec should be amended to fordid raw types too. > > regards, > R?mi From maurizio.cimadamore at oracle.com Fri Mar 19 18:52:47 2021 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 19 Mar 2021 18:52:47 +0000 Subject: Type pattern and raw type In-Reply-To: <1c4479df-1830-f952-1261-898e484cccc2@oracle.com> References: <1493278740.392474.1615806491528.JavaMail.zimbra@u-pem.fr> <1c4479df-1830-f952-1261-898e484cccc2@oracle.com> Message-ID: On 15/03/2021 13:28, Brian Goetz wrote: > Unchecked conversions are forbidden because of type pollution, that why you can not write > if (o instanceof Foo foo) { > > Raw type conversions also leads to type pollution, so i think that a type pattern with a raw type should be forbidden too. Note sure I agree here - they are two very different kind of pollutions. In the Foo case the issue is that we might any type that converts to Foo to be treated as a Foo. E.g. Foo -> Foo. That is unsound, and the unsoundness happens in the conversion itself. But when you say `o instanceof Foo f`, you are not introducing any pollution. The pollution will be introduced when (and if) the raw type will be accessed. Honestly, I'm not even sure this deserves a warning (other than the customary "raw-type usage" warning - not an "unchecked" warning, to be clear). Maurizio From maurizio.cimadamore at oracle.com Fri Mar 19 19:20:16 2021 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 19 Mar 2021 19:20:16 +0000 Subject: Looking ahead: pattern assignment In-Reply-To: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> References: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> Message-ID: <206c7ef1-ab61-adbb-9625-03ab84d09f99@oracle.com> On 12/03/2021 19:58, Brian Goetz wrote: > While this is not on the immediate horizon, I think we are ready to > put the pieces together for pattern assignment.? I think we now > understand the form this has to take, and the constraints around it. Yes please! :-) > > Just as we were successfully able to unify pattern variables with > locals*, I would like to be able to unify pattern assignment with > assignment. > > A pattern assignment takes the form: > > ??? P = e > > where P is a pattern with at least one binding variable that is total > (perhaps with remainder) on the type of e.? (If P has some remainder > on the type of e, then the assignment will throw NPE or ICCE.)? All > bindings in P are in scope and DA for the remainder of the block in > which P appears, just as with local variable declaration. > > Pattern assignment should work in all of the following contexts: > > ?- Assignment statements: P = e > ?- foreach-loops: for (P : e) { ... } > ?- (optional) try-with-resources: try (P = e) { ... } > ?- (optional) method formals: void m(Point(var x, var y) p) { ... } > ?- (optional) lambda formals: (Point(var x, var y) p) -> { ... } It is easy (and normal) here, I think, to be a little confused. At the beginning I was thinking "boy inference of lambdas with target typing AND patterns is gonna make javac cry" - but I think the key to a lot of these is that whenever you see a pattern declaration, it's like if you had an _explicit_ type. So, while there might be magic at runtime to decompose the value into the binding variables, from a static perspective, typing the lambda: Point(var x, var y) -> distance(x, y) is no different than typing this: Point p -> distance(p.x, p.y) Crucially, the pattern provides an explicit type (including generic type arguments, at least initially). From here I guess the next step would be, for a lambda like this: Box(String s) -> ... to avoid the outer `` - but I'm not sure about that step. I think if we treat a pattern as a replacement for an explicit type, things works nicely and there's a simple user model to explain. If we make it too magic, it could be more concise, but also more confusing. As for try-with-resources, as another email alluded to, I think that again the explicit type/pattern replacement gives us a guidance as to how to interpret that behavior. It is the outermost type that has to be AutoCloseable, and the only thing that will be closed at the end of the TWR. As Remi pointed out, this idiom could still be helpful where the AutoCloseable has some "structure" that the TWR block wants to access to. > > (And I'm sure I forgot some.) Exceptions? That seems one of the convenient ones: catch(AssertionError(String msg)) { ?? // use msg } > > Minimally, we have to align the semantics of local variable > declaration with assignment with that of pattern matching; `T t = e` > should have the same semantics whether we view it as a local > declaration plus assignment, or a pattern match.? This means that we > have to, minimally, align the assignment-context conversions in JLS > 5.? (If we wish to support patterns in method/lambda formals, we also > have to align the method-invocation context conversions.) > > Early in the game, we explored supporting partial patterns in pattern > assignment, such as: > > ??? let P = e > ??? else { ... } > > where the `else` clause must either complete abruptly, or assign to > all bindings declared in `P`.? (It wasn't until we unified pattern > variables with locals that there was an obvious way to specify the > latter.)? While this construct is sound, it is in tension with other > uses of pattern assignment: > > ?- (syntactic) Its pretty hard to imagine an `else` clause without > introducing the assignment with some sort of keyword, such as `let`, > but this limits its usefulness in other contexts such as method > parameter declarations; > ?- (pragmatic) It just doesn't add very much value; if the else > throws, it is no less verbose than an if-else. > I was thinking that maybe another way to get at that is by using unchecked exceptions - e.g. if pattern failure raised a well-known unchecked exception, then users could have a chance (if they want) at looking as to why things failed. try { ?? P p = e; ?? ... } catch (...) The problem with this though is that the handler code is very distant from where the failure has happened (unlike in let/else). And we can't really do: P p; try { ? p = e; } catch (...) Because the proposed pattern assignment doesn't support some form of blank declaration - e.g. a way to say: Point(int x, int y); if (...) { ??? // assign pattern from here } else { ?? // assign pattern from here } Is this something we view as a limitation? Cheers Maurizio From brian.goetz at oracle.com Fri Mar 19 19:35:15 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 19 Mar 2021 15:35:15 -0400 Subject: Looking ahead: pattern assignment In-Reply-To: <206c7ef1-ab61-adbb-9625-03ab84d09f99@oracle.com> References: <63f6199a-922f-dd9c-29c7-01d3ce448e4d@oracle.com> <206c7ef1-ab61-adbb-9625-03ab84d09f99@oracle.com> Message-ID: <31f3ff24-9aa6-91d0-8d83-6e587d99ca1d@oracle.com> >> >> Pattern assignment should work in all of the following contexts: >> >> ?- Assignment statements: P = e >> ?- foreach-loops: for (P : e) { ... } >> ?- (optional) try-with-resources: try (P = e) { ... } >> ?- (optional) method formals: void m(Point(var x, var y) p) { ... } >> ?- (optional) lambda formals: (Point(var x, var y) p) -> { ... } > > It is easy (and normal) here, I think, to be a little confused. At the > beginning I was thinking "boy inference of lambdas with target typing > AND patterns is gonna make javac cry" - but I think the key to a lot > of these is that whenever you see a pattern declaration, it's like if > you had an _explicit_ type. So, while there might be magic at runtime > to decompose the value into the binding variables, from a static > perspective, typing the lambda: > > Point(var x, var y) -> distance(x, y) > > is no different than typing this: > > Point p -> distance(p.x, p.y) > Exactly; think of it as a syntactic desugaring ??? Point(var x, var y) p -> e to ??? Point p -> { Point(var x, var y) = p; return e; } My intent was actually to require the top-level variable (p) in lambda/method formals, which emphasizes this. > Crucially, the pattern provides an explicit type (including generic > type arguments, at least initially). > > From here I guess the next step would be, for a lambda like this: > > Box(String s) -> ... > > to avoid the outer `` - but I'm not sure about that step. I > think if we treat a pattern as a replacement for an explicit type, > things works nicely and there's a simple user model to explain. If we > make it too magic, it could be more concise, but also more confusing. > Agree. > I was thinking that maybe another way to get at that is by using > unchecked exceptions - e.g. if pattern failure raised a well-known > unchecked exception, then users could have a chance (if they want) at > looking as to why things failed. > > try { > ?? P p = e; > ?? ... > } catch (...) > > The problem with this though is that the handler code is very distant > from where the failure has happened (unlike in let/else). > Not only that, but if the exception is unchecked, it is really not obvious that the match is even partial.? I like that we require an intrinsically conditional control flow construct (instanceof, switch, catch) for partial patterns, and only allow total patterns in things that "look total". > And we can't really do: > > P p; > try { > ? p = e; > } catch (...) > > Because the proposed pattern assignment doesn't support some form of > blank declaration - e.g. a way to say: > > Point(int x, int y); > if (...) { > ??? // assign pattern from here > } else { > ?? // assign pattern from here > } > > Is this something we view as a limitation? > It's a glass which is either P% full or (100-P)% empty :)? But I would much rather drive towards making what you wrote legal, in some way, rather than make assignment partial in a less-than-fully-transparent way. We've already encountered another place where we might want bind-to-existing: composition of deconstructors/patterns.? Suppose: ??? class A { ??????? int a; ??????? deconstructor(int a) { a = this.a; } ??? } ??? class B extends A { ??????? int b; ?????? deconstructor(int a, int b) { *?????????? super(a);* ?????????? b = this.b; ?????? } ?? } If we can't invoke another pattern with bind-to-one-of-my-bindings, then we'd have to write something like: ?????? deconstructor(int a, int b) { ?????????? super(var aa) = this; ?????????? a = aa; ?????????? b = this.b; ?????? } While this is not the worst thing in the world, it will surely be a persistent irritation.?? So *some* way to say "bind to this variable" possibly under some restrictions (DU?) seems desirable. If we had that, then your if-else would do the trick: ??? int x, y;? // blank locals, therefore DU ??? if (!(target instanceof Point(__my x, __my y)) { ??????? x = y = 0; ??? } From gavin.bierman at oracle.com Mon Mar 22 10:05:22 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Mon, 22 Mar 2021 10:05:22 +0000 Subject: Type pattern and raw type In-Reply-To: References: <1493278740.392474.1615806491528.JavaMail.zimbra@u-pem.fr> <1c4479df-1830-f952-1261-898e484cccc2@oracle.com> Message-ID: <839F8992-DAB3-473B-A9B5-64CA8038F1F3@oracle.com> Agreed, Maurizio. Jan has pushed a patch for this. Now if you compile with `-Xlint:rawtypes` you?ll get a warning for `o instanceof Foo foo` - this was just an oversight. Gavin > On 19 Mar 2021, at 18:52, Maurizio Cimadamore wrote: > > > On 15/03/2021 13:28, Brian Goetz wrote: >> Unchecked conversions are forbidden because of type pollution, that why you can not write >> if (o instanceof Foo foo) { >> >> Raw type conversions also leads to type pollution, so i think that a type pattern with a raw type should be forbidden too. > > Note sure I agree here - they are two very different kind of pollutions. > > In the Foo case the issue is that we might any type that converts to Foo to be treated as a Foo. E.g. Foo -> Foo. That is unsound, and the unsoundness happens in the conversion itself. > > But when you say `o instanceof Foo f`, you are not introducing any pollution. The pollution will be introduced when (and if) the raw type will be accessed. > > Honestly, I'm not even sure this deserves a warning (other than the customary "raw-type usage" warning - not an "unchecked" warning, to be clear). > > Maurizio > From maurizio.cimadamore at oracle.com Mon Mar 22 11:01:57 2021 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 22 Mar 2021 11:01:57 +0000 Subject: Type pattern and raw type In-Reply-To: <839F8992-DAB3-473B-A9B5-64CA8038F1F3@oracle.com> References: <1493278740.392474.1615806491528.JavaMail.zimbra@u-pem.fr> <1c4479df-1830-f952-1261-898e484cccc2@oracle.com> <839F8992-DAB3-473B-A9B5-64CA8038F1F3@oracle.com> Message-ID: On 22/03/2021 10:05, Gavin Bierman wrote: > Agreed, Maurizio. Jan has pushed a patch for this. Now if you compile with `-Xlint:rawtypes` you?ll get a warning for `o instanceof Foo foo` - this was just an oversight. Thanks for the clarification Maurizio > > Gavin > >> On 19 Mar 2021, at 18:52, Maurizio Cimadamore wrote: >> >> >> On 15/03/2021 13:28, Brian Goetz wrote: >>> Unchecked conversions are forbidden because of type pollution, that why you can not write >>> if (o instanceof Foo foo) { >>> >>> Raw type conversions also leads to type pollution, so i think that a type pattern with a raw type should be forbidden too. >> Note sure I agree here - they are two very different kind of pollutions. >> >> In the Foo case the issue is that we might any type that converts to Foo to be treated as a Foo. E.g. Foo -> Foo. That is unsound, and the unsoundness happens in the conversion itself. >> >> But when you say `o instanceof Foo f`, you are not introducing any pollution. The pollution will be introduced when (and if) the raw type will be accessed. >> >> Honestly, I'm not even sure this deserves a warning (other than the customary "raw-type usage" warning - not an "unchecked" warning, to be clear). >> >> Maurizio >> From gavin.bierman at oracle.com Mon Mar 22 12:03:31 2021 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Mon, 22 Mar 2021 12:03:31 +0000 Subject: Draft JEP: Sealed Classes Message-ID: <3BB87395-A65F-43C4-89B0-405BF1D56F21@oracle.com> Dear all, Now JDK 16 is out (yay!) it?s time for us to focus on finalizing Sealed Classes in JDK 17. I have written a draft JEP, which is unchanged (other than some superficial editorial changes) from JEP 397. https://openjdk.java.net/jeps/8260514 (I think Brian?s proposal to consider extending the language in some way to support assignment and switch statements being total over sealed hierarchies is something that can be considered as a JEP in itself.) If you have any comments on the draft JEP, please let me know! Thanks, Gavin From forax at univ-mlv.fr Mon Mar 22 12:54:57 2021 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 22 Mar 2021 13:54:57 +0100 (CET) Subject: Draft JEP: Sealed Classes In-Reply-To: <3BB87395-A65F-43C4-89B0-405BF1D56F21@oracle.com> References: <3BB87395-A65F-43C4-89B0-405BF1D56F21@oracle.com> Message-ID: <1177469482.709391.1616417697425.JavaMail.zimbra@u-pem.fr> > De: "Gavin Bierman" > ?: "amber-spec-experts" > Envoy?: Lundi 22 Mars 2021 13:03:31 > Objet: Draft JEP: Sealed Classes > Dear all, > Now JDK 16 is out (yay!) it?s time for us to focus on finalizing Sealed Classes > in JDK 17. > I have written a draft JEP, which is unchanged (other than some superficial > editorial changes) from JEP 397. > [ https://openjdk.java.net/jeps/8260514 | https://openjdk.java.net/jeps/8260514 > ] > (I think Brian?s proposal to consider extending the language in some way to > support assignment and switch statements being total over sealed hierarchies is > something that can be considered as a JEP in itself.) > If you have any comments on the draft JEP, please let me know! I think it's missing a discussion about lambdas, anonymous classes and local classes that can all extends/implements a sealed type. For Lambdas and anonymous classes, it's easy, they are anonymous, so have no name to list in the permits clause. For Local classes, they are not allowed because they may not be visible sealed interface I {} // I don't see A from here void foo() { record A() implements I {} } But i think we may want to relax that rule a little to be able to declare a sealed type and an interface if they are at the same "nested level" @Test void aTestMethod() { sealed interface I {} record A() implements I {} } It's very convenient when you want different hierarchies when testing things like reflection inside JUnit. I also think we should add a discussion about why using the keyword "non-sealed" instead of something like "open", i.e why using a hyphen separated keyword instead of a "grammar local" keyword, because it's a question often asked when i've explained that JEP. Something along the line that hyphen separated keywords are a lexer issue so it's less work for all the Java ecosystem, all the tools that parse java code, than a using "grammar local" keyword which requires to tweak the parser or the grammar (or both). And some minor remarks, in the JEP, the example abstract sealed class Shape { class Circle extends Shape { ... } ... } The class Circle should be declared static if we want to have the same behavior than the example above in the JEP. Also, getPermittedSubClasses() returns a java.lang.Class not a java.lang.Class ( is missing). > Thanks, > Gavin regards, R?mi From brian.goetz at oracle.com Mon Mar 22 14:12:58 2021 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 22 Mar 2021 10:12:58 -0400 Subject: Draft JEP: Sealed Classes In-Reply-To: <1177469482.709391.1616417697425.JavaMail.zimbra@u-pem.fr> References: <3BB87395-A65F-43C4-89B0-405BF1D56F21@oracle.com> <1177469482.709391.1616417697425.JavaMail.zimbra@u-pem.fr> Message-ID: > > I think it's missing a discussion about lambdas, anonymous classes and > local classes that can all extends/implements a sealed type. > For Lambdas and anonymous classes, it's easy, they are anonymous, so > have no name to list in the permits clause. Yes, we should state these exclusions explicitly. > For Local classes, they are not allowed because they may not be visible > ? sealed interface I {}? // I don't see A from here > ? void foo() { > ??? record A() implements I {} > ? } > > But i think we may want to relax that rule a little to be able to > declare a sealed type and an interface if they are at the same "nested > level" > ? @Test > ? void aTestMethod() { > ??? sealed interface I {} > ??? record A() implements I {} > ? } > > It's very convenient when you want different hierarchies when testing > things like reflection inside JUnit. I'm sympathetic to these arguments, but I'm not sure this is the time to revisit them.? Yes, we could expand the usefulness of sealed types in "local world", but the return-on-complexity is pretty weak. Additionally, we've had some discussions about regularizing the rules surrounding local classes, and these interact with some other ideas in the pipeline.? I'd rather take a note to address this when we address local classes more comprehensively, rather than piecemeal. > > I also think we should add a discussion about why using the keyword > "non-sealed" instead of something like "open", i.e why using a hyphen > separated keyword instead of a "grammar local" keyword, > because it's a question often asked when i've explained that JEP. > Something along the line that hyphen separated keywords are a lexer > issue so it's less work for all the Java ecosystem, all the tools that > parse java code, than a using "grammar local" keyword which requires > to tweak the parser or the grammar (or both). We should refer to: https://openjdk.java.net/jeps/8223002? for this. > And some minor remarks, > in the JEP, the example > ? abstract sealed class Shape { > ??? class Circle extends Shape { ... } > ??? ... > ? } > The class Circle should be declared static if we want to have the same > behavior than the example above in the JEP. Right. From forax at univ-mlv.fr Mon Mar 22 20:26:33 2021 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Mon, 22 Mar 2021 21:26:33 +0100 (CET) Subject: Draft JEP: Sealed Classes In-Reply-To: References: <3BB87395-A65F-43C4-89B0-405BF1D56F21@oracle.com> <1177469482.709391.1616417697425.JavaMail.zimbra@u-pem.fr> Message-ID: <737101985.1110327.1616444793135.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Remi Forax" , "Gavin Bierman" > Cc: "amber-spec-experts" > Envoy?: Lundi 22 Mars 2021 15:12:58 > Objet: Re: Draft JEP: Sealed Classes >> I think it's missing a discussion about lambdas, anonymous classes and local >> classes that can all extends/implements a sealed type. >> For Lambdas and anonymous classes, it's easy, they are anonymous, so have no >> name to list in the permits clause. > Yes, we should state these exclusions explicitly. >> For Local classes, they are not allowed because they may not be visible >> sealed interface I {} // I don't see A from here >> void foo() { >> record A() implements I {} >> } >> But i think we may want to relax that rule a little to be able to declare a >> sealed type and an interface if they are at the same "nested level" >> @Test >> void aTestMethod() { >> sealed interface I {} >> record A() implements I {} >> } >> It's very convenient when you want different hierarchies when testing things >> like reflection inside JUnit. > I'm sympathetic to these arguments, but I'm not sure this is the time to revisit > them. Yes, we could expand the usefulness of sealed types in "local world", but > the return-on-complexity is pretty weak. > Additionally, we've had some discussions about regularizing the rules > surrounding local classes, and these interact with some other ideas in the > pipeline. I'd rather take a note to address this when we address local classes > more comprehensively, rather than piecemeal. Ok, i don't think there is a lot of complexity here but there is clearly more complexity that just a sealed type doesn't permit local classes. >> I also think we should add a discussion about why using the keyword "non-sealed" >> instead of something like "open", i.e why using a hyphen separated keyword >> instead of a "grammar local" keyword, >> because it's a question often asked when i've explained that JEP. Something >> along the line that hyphen separated keywords are a lexer issue so it's less >> work for all the Java ecosystem, all the tools that parse java code, than a >> using "grammar local" keyword which requires to tweak the parser or the grammar >> (or both). > We should refer to: [ https://openjdk.java.net/jeps/8223002 | > https://openjdk.java.net/jeps/8223002 ] for this. Yes R?mi