From gavin.bierman at oracle.com Wed Mar 1 00:42:31 2023 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Wed, 1 Mar 2023 00:42:31 +0000 Subject: Draft JEPs for Patterns in switch and Record Patterns In-Reply-To: References: <322686D2-BD86-4F05-BA33-C4017C772DB5@oracle.com> Message-ID: <73047CA6-1D68-43C9-8D86-070346732586@oracle.com> Fixed. Thanks. > On 28 Feb 2023, at 21:11, John Hendrikx wrote: > > In https://openjdk.org/jeps/8300542, the example in "Improved support for enum constant case labels" seems to be incorrect. > > I think the `goodEnumSwitch2`: > > static void goodEnumSwitch2(Currency c) { > > Should use `Coin` as parameter: > > static void goodEnumSwitch2(Coin c) { > > Also, the enum is called `Coin`, but `Coins` is used a qualifier in several places. > > --John > > ------ Original Message ------ > From "Gavin Bierman" > To "amber-dev at openjdk.org" > Cc "amber-spec-experts" > Date 28/02/2023 17:21:42 > Subject Draft JEPs for Patterns in switch and Record Patterns > >> Hello, >> >> We are planning to finalize the two pattern matching JEPs in JDK 21. Drafts of >> these final JEPs are available here: >> >> Pattern matching for switch: https://openjdk.org/jeps/8300542 >> Record patterns: https://openjdk.org/jeps/8300541 >> >> We're proposing some small changes from the preview versions about to appear in >> JDK 20. These include: >> >> - We're dropping parenthesized patterns. They were leftover from a previous >> version of patterns, and they weren't used very much. They complicate the spec >> for not a lot of gain. >> >> - We're going to support case labels that are the qualified name of enum >> constants, and allow switches over non-enum types to have enum case labels >> provided they use the qualified names of the enum constants and these labels >> are assignment compatible with the switch type. >> >> - We're dropping the support for record patterns in the header of enhanced for >> statements. These will re-appear in a separate forthcoming JEP. >> >> Please take a look at these new JEPs and give us your feedback (either on this >> list or directly to me). >> >> Thanks, >> Gavin >> > From gavin.bierman at oracle.com Wed Mar 1 00:42:40 2023 From: gavin.bierman at oracle.com (Gavin Bierman) Date: Wed, 1 Mar 2023 00:42:40 +0000 Subject: Draft JEPs for Patterns in switch and Record Patterns In-Reply-To: <1668423924.590526.1677619805010.JavaMail.zimbra@u-pem.fr> References: <322686D2-BD86-4F05-BA33-C4017C772DB5@oracle.com> <1668423924.590526.1677619805010.JavaMail.zimbra@u-pem.fr> Message-ID: Fixed. Thanks. > On 28 Feb 2023, at 21:30, Remi Forax wrote: > > ----- Original Message ----- >> From: "John Hendrikx" >> To: "Gavin Bierman" , "amber-dev" >> Cc: "amber-spec-experts" >> Sent: Tuesday, February 28, 2023 10:11:39 PM >> Subject: Re: Draft JEPs for Patterns in switch and Record Patterns > >> In https://openjdk.org/jeps/8300542, the example in "Improved support >> for enum constant case labels" seems to be incorrect. >> >> I think the `goodEnumSwitch2`: >> >> static void goodEnumSwitch2(Currency c) { >> >> Should use `Coin` as parameter: >> >> static void goodEnumSwitch2(Coin c) { >> >> Also, the enum is called `Coin`, but `Coins` is used a qualifier in >> several places. >> >> --John > > Also s.equalsIgnoreCase("YES") is better than s.toUpperCase().equals("YES") > > R?mi > >> >> ------ Original Message ------ >> From "Gavin Bierman" >> To "amber-dev at openjdk.org" >> Cc "amber-spec-experts" >> Date 28/02/2023 17:21:42 >> Subject Draft JEPs for Patterns in switch and Record Patterns >> >>> Hello, >>> >>> We are planning to finalize the two pattern matching JEPs in JDK 21. Drafts of >>> these final JEPs are available here: >>> >>> Pattern matching for switch: https://openjdk.org/jeps/8300542 >>> Record patterns: https://openjdk.org/jeps/8300541 >>> >>> We're proposing some small changes from the preview versions about to appear in >>> JDK 20. These include: >>> >>> - We're dropping parenthesized patterns. They were leftover from a previous >>> version of patterns, and they weren't used very much. They complicate the spec >>> for not a lot of gain. >>> >>> - We're going to support case labels that are the qualified name of enum >>> constants, and allow switches over non-enum types to have enum case labels >>> provided they use the qualified names of the enum constants and these labels >>> are assignment compatible with the switch type. >>> >>> - We're dropping the support for record patterns in the header of enhanced for >>> statements. These will re-appear in a separate forthcoming JEP. >>> >>> Please take a look at these new JEPs and give us your feedback (either on this >>> list or directly to me). >>> >>> Thanks, >>> Gavin From redio.development at gmail.com Sat Mar 4 09:55:30 2023 From: redio.development at gmail.com (Red IO) Date: Sat, 4 Mar 2023 10:55:30 +0100 Subject: Expanding the "expression syntax" Message-ID: Currently we have the switch expression as the only expression returning a result. Example: boolean b = switch (1) { case 0 -> false; case 1 -> { yield true; } }; The idea would be to expand this syntax to different expressions for example the try expression: int i = try { yield Integer.parseInt("Abc"); } catch (NumberFormatException e) { yield -1; }; Or loops: String searched = for(String s : args) { if (s.startsWith("-")) yield s; }; The idea is to make all java control flow expressions able to yield a value. Among many new patterns possible like the examples above it would for example "clean up" the exception catching initialization: Foo foo = null; try { foo = new Foo(); } catch (SomeException e) { //do something or nothing } finally { //maybe change the value again } To var foo = try { yield new Foo(); } catch (SomeException e) { //throw or yield } finally { //throw or do nothing }; Another benefit especially in this example is the clearer state of the result. In an yielding expression you have to either yield or throw. Also subsequent manipulation in for example the finally block is not possible making the source of the returned result clear to identify. Great regards RedIODev -------------- next part -------------- An HTML attachment was scrubbed... URL: From tom_l64 at hotmail.com Sat Mar 4 23:49:37 2023 From: tom_l64 at hotmail.com (Tom L) Date: Sun, 5 Mar 2023 00:49:37 +0100 Subject: Language feature to improve checked exceptions Message-ID: Hello, it's the first time I send an email here, I have no idea how things work, so I hope I am doing things somewhat correctly. I wanted to make a suggestion about exceptions : Checked exceptions are used as a meant of a second return type, which shouldn't happen in most cases, and then, depending of what the caller wants to do, it will handle it in a certain way. So in some sort, a checked exception is like part of the return type, like you would have a Result, compared to unchecked exception in java which are just supposed to be bugs. But this feature causes some burden, so much so that some languages, even languages compiling to java (ie kotlin) got rid of checked exceptions, and other languages like Rust use a Result which, which, with enough language constructs, can be quite good. While I agree that not having checked exceptions nor Result but instead only unchecked ones would be a bad idea, because it would mean that a part of the return type is unknown, which we wouldn't want in a strongly typed language, I believe some action needs to be taken. In my opinion, one of its burdens is caused by the try-catch language feature : String text; try { text = Files.readString(somePath); } catch(IOException ex) { throw new UncheckedIOException(ex); } //do something with text This is a common example, of how you would use it : you want to read a string, and an IO error shouldn't happen, so you fail-fast The reason why I didn't use text inside the catch, is because the catch should only be for this specific exception, and also it would add another level of nesting, which would start to hurt when other ifs or try-catches appear. This code is boilerplate and is error-prone, since you always have to repeat the same lines, it is also weird for any non java programmer. And this is far from being the worst, because another common example is with streams, since you can't throw checked exception, you have to handle each time, in each stream operation, and even if you wrap this code in methods and use method references, it's still far less readable than having a short lambda where you see exactly what the stream is doing. If checked exceptions were instead a Result type, a solution to this problem would be to make a unwrap() method (like in Rust, or like with Java's Optional#orElseThrow()) String text = Files.readString(somePath).unwrap(); So my suggestion is that, since catching is a language feature, it can only be dealt with another language feature : String text = Files.readString(somePath) throw IOException ex as new UncheckedIOException(ex); If this method throws an IOException, it will rethrow it as an UncheckedIOException. About the syntax, I used "throw" since it's an already used keyword, but depending of the meaning, it could be "catch" or whatever, and "as" could be "->" if using a contextual keyword is too much. This code could even be simplied as the following with new methods or language features, in the future : String text = Files.readString(somePath) throw IOException ex as ex.unchecked(); String text = Files.readString(somePath) throw IOException as unchecked; etc. Unchecked part could either be the same exception except the compiler ignores it (I know this is possible since it's possible to throw a checked exception as an unchecked), the idea is that since you are telling the compiler that if an exception happens, then it should fail-fast, then the compiler should be able to say "ok, I trust you". An alternative would be that the unchecked simply wrap it in a RuntimeException or a specific unchecked exception. So, what's the point of this, does it only serve this use case ? So first, is it important to note that it isn't just for checked -> fail-fast, but also checked -> some other checked, when exception conversion is needed, which can be useful, and also unchecked -> checked/unchecked, for example if an API only provides an unchecked like Integer#parseInt. Now about the use case : this syntax, isn't just a compressed try-catch, it's also an expression, which is very important, because not only you can very clearly handle this kind of cases, but it can provide easy to read, concise code in lambdas and assignments, for example : try (var files = Files.list(path) { return files.filter(this::matcher) .map(p -> Files.readString(p) throw IOException as unchecked)//Possible syntax Files::readString throw IOException as unchecked ? .toList(); } And in the future, a new method could be added for streams called .continueOnFailure(IOExcepion.class) (or UncheckedIOException if the unchecked wraps the exception instead of marking it as unchecked) for example, which would continue even if there is an exception. An additional syntax could also be provided for cases where catching is used as an if else : OptionalInt parseInt(String s) { return OptionalInt.of(Integer.parseInt(s)) catch NumberFormatException -> OptionalInt.empty(); // Using -> syntax here instead of "as" to show that it can also make sense } I hope it can fix this unholy war of checked exceptions which never seem to advance. Sincerely. -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Sun Mar 5 02:24:38 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 4 Mar 2023 21:24:38 -0500 Subject: Language feature to improve checked exceptions In-Reply-To: References: Message-ID: <5b723c33-d38f-97f7-e5d4-ad7c345a74ff@oracle.com> I have some sympathy for the desire to optimize catch-and-wrap; doing this the regular way is indeed syntactically painful when the actual business logic is small (which it often is.) Catch-and-wrap works well as an abstraction idiom when turning a low-level exception (e.g., IOException) into a higher-level one (e.g., XMLException); as higher-level library-code delegates to lower-level library code, it will want to remap low-level errors to higher-level ones.? (Particularly interesting is catch-and-wrap at the method declaration level, with some sort of `throws X as Y`, as it represents the catch-and-wrap rules declaratively rather than imperatively.) However, the examples you give of catch-and-wrap are not really catch-and-wrap; they are catch-and-pretend-they-didn't-happen (by turning checked exceptions into unchecked ones.)? While this would surely be popular among the "checked exceptions suck" crowd, this is not making error handling more reliable, it is just making it easier to ignore errors. The Result approach is both safe and honest (and can be implemented today without language help), but is more foreign to Java developers.? It operates by moving the side-channel result into the main channel, which enables monadic composition (turning a partial operation into a total one returning a Success|Fail union).? With pattern matching in the language, this gets even more attractive.? Still, I suspect that most of the "checked exceptions suck" crowd wouldn't thank us for kicking off the massive, decade-long migration from checked exceptions to the Either monad. Because of the billions of lines of existing Java code, and the almost-billion developer-years of experience with the status quo, This problem does not admit easy answers.? We've considered various variations of the ideas suggested here, but so far we've not found the one we want to commit to for the ages. On 3/4/2023 6:49 PM, Tom L wrote: > > Hello, it's the first time I send an email here, I have no idea how > things work, so I hope I am doing things somewhat correctly. > > I wanted to make a suggestion about exceptions : > > Checked exceptions are used as a meant of a second return type, which > shouldn't happen in most cases, and then, depending of what the caller > wants to do, it will handle it in a certain way. So in some sort, a > checked exception is like part of the return type, like you would have > a Result, compared to unchecked exception in java > which are just supposed to be bugs. > > But this feature causes some burden, so much so that some languages, > even languages compiling to java (ie kotlin) got rid of checked > exceptions, and other languages like Rust use a Result Failure> which, which, with enough language constructs, can be quite good. > > While I agree that not having checked exceptions nor Result but > instead only unchecked ones would be a bad idea, because it would mean > that a part of the return type is unknown, which we wouldn't want in a > strongly typed language, I believe some action needs to be taken. > > In my opinion, one of its burdens is caused by the try-catch language > feature : > > String text; > > try { > > ??????????????? text = Files.readString(somePath); > > } catch(IOException ex) { > > ??????????????? throw new UncheckedIOException(ex); > > } > > //do something with text > > This is a common example, of how you would use it : you want to read a > string, and an IO error shouldn't happen, so you fail-fast > > The reason why I didn't use text inside the catch, is because the > catch should only be for this specific exception, and also it would > add another level of nesting, which would start to hurt when other ifs > or try-catches appear. > > This code is boilerplate and is error-prone, since you always have to > repeat the same lines, it is also weird for any non java programmer. > > And this is far from being the worst, because another common example > is with streams, since you can't throw checked exception, you have to > handle each time, in each stream operation, and even if you wrap this > code in methods and use method references, it's still far less > readable than having a short lambda where you see exactly what the > stream is doing. > > If checked exceptions were instead a Result type, a solution to this > problem would be to make a unwrap() method (like in Rust, or like with > Java's Optional#orElseThrow()) > > String text = Files.readString(somePath).unwrap(); > > So my suggestion is that, since catching is a language feature, it can > only be dealt with another language feature : > > String text = Files.readString(somePath) throw IOException ex as new > UncheckedIOException(ex); > > If this method throws an IOException, it will rethrow it as an > UncheckedIOException. > > About the syntax, I used "throw" since it's an already used keyword, > but depending of the meaning, it could be "catch" or whatever, and > "as" could be "->" if using a contextual keyword is too much. > > This code could even be simplied as the following with new methods or > language features, in the future : > > String text = Files.readString(somePath) throw IOException ex as > ex.unchecked(); > > String text = Files.readString(somePath) throw IOException as unchecked; > > etc. > > Unchecked part could either be the same exception except the compiler > ignores it (I know this is possible since it's possible to throw a > checked exception as an unchecked), the idea is that since you are > telling the compiler that if an exception happens, then it should > fail-fast, then the compiler should be able to say "ok, I trust you". > > An alternative would be that the unchecked simply wrap it in a > RuntimeException or a specific unchecked exception. > > So, what's the point of this, does it only serve this use case ? > > So first, is it important to note that it isn't just for checked -> > fail-fast, but also checked -> some other checked, when exception > conversion is needed, which can be useful, and also unchecked -> > checked/unchecked, for example if an API only provides an unchecked > like Integer#parseInt. > > Now about the use case : this syntax, isn't just a compressed > try-catch, it's also an expression, which is very important, because > not only you can very clearly handle this kind of cases, but it can > provide easy to read, concise code in lambdas? and assignments, for > example : > > try (var files = Files.list(path) { > > ??????????????? return files.filter(this::matcher) > > .map(p -> Files.readString(p) throw IOException as > unchecked)//Possible syntax Files::readString throw IOException as > unchecked ? > > .toList(); > > } > > And in the future, a new method could be added for streams called > .continueOnFailure(IOExcepion.class) (or UncheckedIOException if the > unchecked wraps the exception instead of marking it as unchecked) for > example, which would continue even if there is an exception. > > An additional syntax could also be provided for cases where catching > is used as an if else : > > OptionalInt parseInt(String s) { > > ??????????????? return OptionalInt.of(Integer.parseInt(s)) catch > NumberFormatException -> OptionalInt.empty(); ?????????? // Using -> > syntax here instead of "as" to show that it can also make sense > > } > > I hope it can fix this unholy war of checked exceptions which never > seem to advance. > > Sincerely. > -------------- next part -------------- An HTML attachment was scrubbed... URL: From redio.development at gmail.com Sun Mar 5 06:58:54 2023 From: redio.development at gmail.com (Red IO) Date: Sun, 5 Mar 2023 07:58:54 +0100 Subject: Language feature to improve checked exceptions In-Reply-To: References: Message-ID: This looks to target a similar problem as my suggestion to treat every java control flow element as an expression potentially returning a result. Your first example would look like this with my concept: String text = try { yield Files.readString(somePath); } catch(IOException ex) { throw new UncheckedIOException(ex); }; It would stay true to the try catch finally syntax and would reuse the already existing expression yielding syntax from switch expressions while still removing the unsound partial initialized field. Great regards RedIODev On Sun, Mar 5, 2023, 00:49 Tom L wrote: > Hello, it's the first time I send an email here, I have no idea how things > work, so I hope I am doing things somewhat correctly. > > > > I wanted to make a suggestion about exceptions : > > Checked exceptions are used as a meant of a second return type, which > shouldn't happen in most cases, and then, depending of what the caller > wants to do, it will handle it in a certain way. So in some sort, a checked > exception is like part of the return type, like you would have a > Result, compared to unchecked exception in java which are > just supposed to be bugs. > > But this feature causes some burden, so much so that some languages, even > languages compiling to java (ie kotlin) got rid of checked exceptions, and > other languages like Rust use a Result which, which, with > enough language constructs, can be quite good. > > While I agree that not having checked exceptions nor Result but instead > only unchecked ones would be a bad idea, because it would mean that a part > of the return type is unknown, which we wouldn't want in a strongly typed > language, I believe some action needs to be taken. > > > > In my opinion, one of its burdens is caused by the try-catch language > feature : > > String text; > > try { > > text = Files.readString(somePath); > > } catch(IOException ex) { > > throw new UncheckedIOException(ex); > > } > > //do something with text > > This is a common example, of how you would use it : you want to read a > string, and an IO error shouldn't happen, so you fail-fast > > The reason why I didn't use text inside the catch, is because the catch > should only be for this specific exception, and also it would add another > level of nesting, which would start to hurt when other ifs or try-catches > appear. > > This code is boilerplate and is error-prone, since you always have to > repeat the same lines, it is also weird for any non java programmer. > > And this is far from being the worst, because another common example is > with streams, since you can't throw checked exception, you have to handle > each time, in each stream operation, and even if you wrap this code in > methods and use method references, it's still far less readable than having > a short lambda where you see exactly what the stream is doing. > > If checked exceptions were instead a Result type, a solution to this > problem would be to make a unwrap() method (like in Rust, or like with > Java's Optional#orElseThrow()) > > String text = Files.readString(somePath).unwrap(); > > > > So my suggestion is that, since catching is a language feature, it can > only be dealt with another language feature : > > String text = Files.readString(somePath) throw IOException ex as new > UncheckedIOException(ex); > > If this method throws an IOException, it will rethrow it as an > UncheckedIOException. > > About the syntax, I used "throw" since it's an already used keyword, but > depending of the meaning, it could be "catch" or whatever, and "as" could > be "->" if using a contextual keyword is too much. > > This code could even be simplied as the following with new methods or > language features, in the future : > > String text = Files.readString(somePath) throw IOException ex as > ex.unchecked(); > > String text = Files.readString(somePath) throw IOException as unchecked; > > etc. > > Unchecked part could either be the same exception except the compiler > ignores it (I know this is possible since it's possible to throw a checked > exception as an unchecked), the idea is that since you are telling the > compiler that if an exception happens, then it should fail-fast, then the > compiler should be able to say "ok, I trust you". > > An alternative would be that the unchecked simply wrap it in a > RuntimeException or a specific unchecked exception. > > > > So, what's the point of this, does it only serve this use case ? > > So first, is it important to note that it isn't just for checked -> > fail-fast, but also checked -> some other checked, when exception > conversion is needed, which can be useful, and also unchecked -> > checked/unchecked, for example if an API only provides an unchecked like > Integer#parseInt. > > Now about the use case : this syntax, isn't just a compressed try-catch, > it's also an expression, which is very important, because not only you can > very clearly handle this kind of cases, but it can provide easy to read, > concise code in lambdas and assignments, for example : > > try (var files = Files.list(path) { > > return files.filter(this::matcher) > > .map(p -> > Files.readString(p) throw IOException as unchecked)//Possible syntax > Files::readString throw IOException as unchecked ? > > .toList(); > > } > > And in the future, a new method could be added for streams called > .continueOnFailure(IOExcepion.class) (or UncheckedIOException if the > unchecked wraps the exception instead of marking it as unchecked) for > example, which would continue even if there is an exception. > > An additional syntax could also be provided for cases where catching is > used as an if else : > > OptionalInt parseInt(String s) { > > return OptionalInt.of(Integer.parseInt(s)) catch > NumberFormatException -> OptionalInt.empty(); // Using -> syntax > here instead of "as" to show that it can also make sense > > } > > > > I hope it can fix this unholy war of checked exceptions which never seem > to advance. > > > > Sincerely. > -------------- next part -------------- An HTML attachment was scrubbed... URL: From amaembo at gmail.com Sun Mar 5 08:54:21 2023 From: amaembo at gmail.com (Tagir Valeev) Date: Sun, 5 Mar 2023 09:54:21 +0100 Subject: Language feature to improve checked exceptions In-Reply-To: References: Message-ID: Hello! You can make something like this with a library code, using existing Java language functionality (you don't even need unchecked casts!). Here's a rough sketch: record Rethrower(Class cls, Function mapper) { public void run(ThrowableRunnable r) throws DST { get(() -> { r.run(); return null; }); } public R get(ThrowableComputable r) throws DST { try { return r.get(); } catch (Throwable t) { if (cls.isInstance(t)) { DST dst = mapper.apply(cls.cast(t)); dst.setStackTrace(t.getStackTrace()); throw dst; } if (t instanceof RuntimeException re) { throw re; } if (t instanceof Error e) { throw e; } throw new IllegalArgumentException("Supplied function throws undeclared checked exception", t); } } } @FunctionalInterface interface ThrowableRunnable { void run() throws E; } @FunctionalInterface interface ThrowableComputable { R get() throws E; } Could be used like this: List list = files.map(p -> new Rethrower<>(IOException.class, UncheckedIOException::new).get(() -> Files.readString(p))) .toList(); Note that this allows to extract and reuse a particular rethrow strategy: static final Rethrower IO_RETHROWER = new Rethrower<>(IOException.class, UncheckedIOException::new ); ... List list = files.map(p -> IO_RETHROWER.get(() -> Files.readString(p ))).toList(); With best regards, Tagir Valeev. On Sun, Mar 5, 2023 at 12:49 AM Tom L wrote: > Hello, it's the first time I send an email here, I have no idea how things > work, so I hope I am doing things somewhat correctly. > > > > I wanted to make a suggestion about exceptions : > > Checked exceptions are used as a meant of a second return type, which > shouldn't happen in most cases, and then, depending of what the caller > wants to do, it will handle it in a certain way. So in some sort, a checked > exception is like part of the return type, like you would have a > Result, compared to unchecked exception in java which are > just supposed to be bugs. > > But this feature causes some burden, so much so that some languages, even > languages compiling to java (ie kotlin) got rid of checked exceptions, and > other languages like Rust use a Result which, which, with > enough language constructs, can be quite good. > > While I agree that not having checked exceptions nor Result but instead > only unchecked ones would be a bad idea, because it would mean that a part > of the return type is unknown, which we wouldn't want in a strongly typed > language, I believe some action needs to be taken. > > > > In my opinion, one of its burdens is caused by the try-catch language > feature : > > String text; > > try { > > text = Files.readString(somePath); > > } catch(IOException ex) { > > throw new UncheckedIOException(ex); > > } > > //do something with text > > This is a common example, of how you would use it : you want to read a > string, and an IO error shouldn't happen, so you fail-fast > > The reason why I didn't use text inside the catch, is because the catch > should only be for this specific exception, and also it would add another > level of nesting, which would start to hurt when other ifs or try-catches > appear. > > This code is boilerplate and is error-prone, since you always have to > repeat the same lines, it is also weird for any non java programmer. > > And this is far from being the worst, because another common example is > with streams, since you can't throw checked exception, you have to handle > each time, in each stream operation, and even if you wrap this code in > methods and use method references, it's still far less readable than having > a short lambda where you see exactly what the stream is doing. > > If checked exceptions were instead a Result type, a solution to this > problem would be to make a unwrap() method (like in Rust, or like with > Java's Optional#orElseThrow()) > > String text = Files.readString(somePath).unwrap(); > > > > So my suggestion is that, since catching is a language feature, it can > only be dealt with another language feature : > > String text = Files.readString(somePath) throw IOException ex as new > UncheckedIOException(ex); > > If this method throws an IOException, it will rethrow it as an > UncheckedIOException. > > About the syntax, I used "throw" since it's an already used keyword, but > depending of the meaning, it could be "catch" or whatever, and "as" could > be "->" if using a contextual keyword is too much. > > This code could even be simplied as the following with new methods or > language features, in the future : > > String text = Files.readString(somePath) throw IOException ex as > ex.unchecked(); > > String text = Files.readString(somePath) throw IOException as unchecked; > > etc. > > Unchecked part could either be the same exception except the compiler > ignores it (I know this is possible since it's possible to throw a checked > exception as an unchecked), the idea is that since you are telling the > compiler that if an exception happens, then it should fail-fast, then the > compiler should be able to say "ok, I trust you". > > An alternative would be that the unchecked simply wrap it in a > RuntimeException or a specific unchecked exception. > > > > So, what's the point of this, does it only serve this use case ? > > So first, is it important to note that it isn't just for checked -> > fail-fast, but also checked -> some other checked, when exception > conversion is needed, which can be useful, and also unchecked -> > checked/unchecked, for example if an API only provides an unchecked like > Integer#parseInt. > > Now about the use case : this syntax, isn't just a compressed try-catch, > it's also an expression, which is very important, because not only you can > very clearly handle this kind of cases, but it can provide easy to read, > concise code in lambdas and assignments, for example : > > try (var files = Files.list(path) { > > return files.filter(this::matcher) > > .map(p -> > Files.readString(p) throw IOException as unchecked)//Possible syntax > Files::readString throw IOException as unchecked ? > > .toList(); > > } > > And in the future, a new method could be added for streams called > .continueOnFailure(IOExcepion.class) (or UncheckedIOException if the > unchecked wraps the exception instead of marking it as unchecked) for > example, which would continue even if there is an exception. > > An additional syntax could also be provided for cases where catching is > used as an if else : > > OptionalInt parseInt(String s) { > > return OptionalInt.of(Integer.parseInt(s)) catch > NumberFormatException -> OptionalInt.empty(); // Using -> syntax > here instead of "as" to show that it can also make sense > > } > > > > I hope it can fix this unholy war of checked exceptions which never seem > to advance. > > > > Sincerely. > -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Sun Mar 5 18:37:29 2023 From: forax at univ-mlv.fr (Remi Forax) Date: Sun, 5 Mar 2023 19:37:29 +0100 (CET) Subject: Language feature to improve checked exceptions In-Reply-To: <5b723c33-d38f-97f7-e5d4-ad7c345a74ff@oracle.com> References: <5b723c33-d38f-97f7-e5d4-ad7c345a74ff@oracle.com> Message-ID: <27180353.3948403.1678041449526.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "Tom L" , "amber-dev" > Sent: Sunday, March 5, 2023 3:24:38 AM > Subject: Re: Language feature to improve checked exceptions > I have some sympathy for the desire to optimize catch-and-wrap; doing this the > regular way is indeed syntactically painful when the actual business logic is > small (which it often is.) Catch-and-wrap works well as an abstraction idiom > when turning a low-level exception (e.g., IOException) into a higher-level one > (e.g., XMLException); as higher-level library-code delegates to lower-level > library code, it will want to remap low-level errors to higher-level ones. > (Particularly interesting is catch-and-wrap at the method declaration level, > with some sort of `throws X as Y`, as it represents the catch-and-wrap rules > declaratively rather than imperatively.) > However, the examples you give of catch-and-wrap are not really catch-and-wrap; > they are catch-and-pretend-they-didn't-happen (by turning checked exceptions > into unchecked ones.) While this would surely be popular among the "checked > exceptions suck" crowd, this is not making error handling more reliable, it is > just making it easier to ignore errors. A remark, a catch-and-pretend-they-didn't-happen is just a catch-and-wrap without the enclosing context. For example, you can see List foo() { ... List list = stream.map(it -> { try { return IO.something(it); } catch(IOException e) { throw new UncheckedIOException(e); } }).toList(), return list; } but foo() is called like this List list; try { list = foo() } catch(UncheckedIOException e) { throw e.getCause(); } > The Result approach is both safe and honest (and can be implemented today > without language help), but is more foreign to Java developers. It operates by > moving the side-channel result into the main channel, which enables monadic > composition (turning a partial operation into a total one returning a > Success|Fail union). With pattern matching in the language, this gets even more > attractive. Still, I suspect that most of the "checked exceptions suck" crowd > wouldn't thank us for kicking off the massive, decade-long migration from > checked exceptions to the Either monad. >From Java the language POV, returning Either or using a checked exception both side of the same coin, there are semantically equivalent. As a proud member of the "checked exception suck in Java" crowd. The "in Java" is important. Checked exceptions do not suck in the vacuum, it suck in Java because on a higher level is goes against the freedom of composition and because on a lower level the Java type system does not handle them well, no union type (only precise rethrow), no proper way to bundle exceptions into a type parameter. It's the colored function problem [1]. Checked exceptions aka the Result monad does not suck in Rust. Because inherently Rust is a language that consider library composition less important than precise lifetime tracking or asynchronous calls tracking, the whole language relies on colored functions. But Java is not Rust, freedom of composition, freedom to use any libraries whenever it was written, is an important part of the language. That's why Java has a GC, lightweight threads, lambdas, erased generics or the concept of binary backward compatibility. All those features enable free easy composition. Sadly checked exception in Java colors functions, they create an unnecessary artificial barrier to library compositions. That's why they suck in Java. regards, R?mi [1] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ > On 3/4/2023 6:49 PM, Tom L wrote: >> Hello, it's the first time I send an email here, I have no idea how things work, >> so I hope I am doing things somewhat correctly. >> I wanted to make a suggestion about exceptions : >> Checked exceptions are used as a meant of a second return type, which shouldn't >> happen in most cases, and then, depending of what the caller wants to do, it >> will handle it in a certain way. So in some sort, a checked exception is like >> part of the return type, like you would have a Result, >> compared to unchecked exception in java which are just supposed to be bugs. >> But this feature causes some burden, so much so that some languages, even >> languages compiling to java (ie kotlin) got rid of checked exceptions, and >> other languages like Rust use a Result which, which, with >> enough language constructs, can be quite good. >> While I agree that not having checked exceptions nor Result but instead only >> unchecked ones would be a bad idea, because it would mean that a part of the >> return type is unknown, which we wouldn't want in a strongly typed language, I >> believe some action needs to be taken. >> In my opinion, one of its burdens is caused by the try-catch language feature : >> String text; >> try { >> text = Files.readString(somePath); >> } catch(IOException ex) { >> throw new UncheckedIOException(ex); >> } >> //do something with text >> This is a common example, of how you would use it : you want to read a string, >> and an IO error shouldn't happen, so you fail-fast >> The reason why I didn't use text inside the catch, is because the catch should >> only be for this specific exception, and also it would add another level of >> nesting, which would start to hurt when other ifs or try-catches appear. >> This code is boilerplate and is error-prone, since you always have to repeat the >> same lines, it is also weird for any non java programmer. >> And this is far from being the worst, because another common example is with >> streams, since you can't throw checked exception, you have to handle each time, >> in each stream operation, and even if you wrap this code in methods and use >> method references, it's still far less readable than having a short lambda >> where you see exactly what the stream is doing. >> If checked exceptions were instead a Result type, a solution to this problem >> would be to make a unwrap() method (like in Rust, or like with Java's >> Optional#orElseThrow()) >> String text = Files.readString(somePath).unwrap(); >> So my suggestion is that, since catching is a language feature, it can only be >> dealt with another language feature : >> String text = Files.readString(somePath) throw IOException ex as new >> UncheckedIOException(ex); >> If this method throws an IOException, it will rethrow it as an >> UncheckedIOException. >> About the syntax, I used "throw" since it's an already used keyword, but >> depending of the meaning, it could be "catch" or whatever, and "as" could be >> "->" if using a contextual keyword is too much. >> This code could even be simplied as the following with new methods or language >> features, in the future : >> String text = Files.readString(somePath) throw IOException ex as ex.unchecked(); >> String text = Files.readString(somePath) throw IOException as unchecked; >> etc. >> Unchecked part could either be the same exception except the compiler ignores it >> (I know this is possible since it's possible to throw a checked exception as an >> unchecked), the idea is that since you are telling the compiler that if an >> exception happens, then it should fail-fast, then the compiler should be able >> to say "ok, I trust you". >> An alternative would be that the unchecked simply wrap it in a RuntimeException >> or a specific unchecked exception. >> So, what's the point of this, does it only serve this use case ? >> So first, is it important to note that it isn't just for checked -> fail-fast, >> but also checked -> some other checked, when exception conversion is needed, >> which can be useful, and also unchecked -> checked/unchecked, for example if an >> API only provides an unchecked like Integer#parseInt. >> Now about the use case : this syntax, isn't just a compressed try-catch, it's >> also an expression, which is very important, because not only you can very >> clearly handle this kind of cases, but it can provide easy to read, concise >> code in lambdas and assignments, for example : >> try (var files = Files.list(path) { >> return files.filter(this::matcher) >> .map(p -> Files.readString(p) throw IOException as unchecked)//Possible syntax >> Files::readString throw IOException as unchecked ? >> .toList(); >> } >> And in the future, a new method could be added for streams called >> .continueOnFailure(IOExcepion.class) (or UncheckedIOException if the unchecked >> wraps the exception instead of marking it as unchecked) for example, which >> would continue even if there is an exception. >> An additional syntax could also be provided for cases where catching is used as >> an if else : >> OptionalInt parseInt(String s) { >> return OptionalInt.of(Integer.parseInt(s)) catch NumberFormatException -> >> OptionalInt.empty(); // Using -> syntax here instead of "as" to show that it >> can also make sense >> } >> I hope it can fix this unholy war of checked exceptions which never seem to >> advance. >> Sincerely. -------------- next part -------------- An HTML attachment was scrubbed... URL: From ron.pressler at oracle.com Sun Mar 5 19:37:41 2023 From: ron.pressler at oracle.com (Ron Pressler) Date: Sun, 5 Mar 2023 19:37:41 +0000 Subject: Language feature to improve checked exceptions In-Reply-To: <27180353.3948403.1678041449526.JavaMail.zimbra@u-pem.fr> References: <5b723c33-d38f-97f7-e5d4-ad7c345a74ff@oracle.com> <27180353.3948403.1678041449526.JavaMail.zimbra@u-pem.fr> Message-ID: <2CE5B2B1-F326-4C88-8A3D-654E45CAA692@oracle.com> Checked exceptions don?t intrinsically "colour your function? any more (or less) than a return type. Java?s ability to helpfully polymorphise over checked exceptions, however, is more limited. As Brian said, various approaches were considered, but nothing suggests itself as a clear, obvious, and easy win that we?re comfortable committing to. It?s not something we?ve forgotten, just something we?ll need to revisit later on; there?s only so much we can work on at any one time. ? Ron On 5 Mar 2023, at 18:37, Remi Forax > wrote: ________________________________ From: "Brian Goetz" > To: "Tom L" >, "amber-dev" > Sent: Sunday, March 5, 2023 3:24:38 AM Subject: Re: Language feature to improve checked exceptions I have some sympathy for the desire to optimize catch-and-wrap; doing this the regular way is indeed syntactically painful when the actual business logic is small (which it often is.) Catch-and-wrap works well as an abstraction idiom when turning a low-level exception (e.g., IOException) into a higher-level one (e.g., XMLException); as higher-level library-code delegates to lower-level library code, it will want to remap low-level errors to higher-level ones. (Particularly interesting is catch-and-wrap at the method declaration level, with some sort of `throws X as Y`, as it represents the catch-and-wrap rules declaratively rather than imperatively.) However, the examples you give of catch-and-wrap are not really catch-and-wrap; they are catch-and-pretend-they-didn't-happen (by turning checked exceptions into unchecked ones.) While this would surely be popular among the "checked exceptions suck" crowd, this is not making error handling more reliable, it is just making it easier to ignore errors. A remark, a catch-and-pretend-they-didn't-happen is just a catch-and-wrap without the enclosing context. For example, you can see List foo() { ... List list = stream.map(it -> { try { return IO.something(it); } catch(IOException e) { throw new UncheckedIOException(e); } }).toList(), return list; } but foo() is called like this List list; try { list = foo() } catch(UncheckedIOException e) { throw e.getCause(); } The Result approach is both safe and honest (and can be implemented today without language help), but is more foreign to Java developers. It operates by moving the side-channel result into the main channel, which enables monadic composition (turning a partial operation into a total one returning a Success|Fail union). With pattern matching in the language, this gets even more attractive. Still, I suspect that most of the "checked exceptions suck" crowd wouldn't thank us for kicking off the massive, decade-long migration from checked exceptions to the Either monad. From Java the language POV, returning Either or using a checked exception both side of the same coin, there are semantically equivalent. As a proud member of the "checked exception suck in Java" crowd. The "in Java" is important. Checked exceptions do not suck in the vacuum, it suck in Java because on a higher level is goes against the freedom of composition and because on a lower level the Java type system does not handle them well, no union type (only precise rethrow), no proper way to bundle exceptions into a type parameter. It's the colored function problem [1]. Checked exceptions aka the Result monad does not suck in Rust. Because inherently Rust is a language that consider library composition less important than precise lifetime tracking or asynchronous calls tracking, the whole language relies on colored functions. But Java is not Rust, freedom of composition, freedom to use any libraries whenever it was written, is an important part of the language. That's why Java has a GC, lightweight threads, lambdas, erased generics or the concept of binary backward compatibility. All those features enable free easy composition. Sadly checked exception in Java colors functions, they create an unnecessary artificial barrier to library compositions. That's why they suck in Java. regards, R?mi [1] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ On 3/4/2023 6:49 PM, Tom L wrote: Hello, it's the first time I send an email here, I have no idea how things work, so I hope I am doing things somewhat correctly. I wanted to make a suggestion about exceptions : Checked exceptions are used as a meant of a second return type, which shouldn't happen in most cases, and then, depending of what the caller wants to do, it will handle it in a certain way. So in some sort, a checked exception is like part of the return type, like you would have a Result, compared to unchecked exception in java which are just supposed to be bugs. But this feature causes some burden, so much so that some languages, even languages compiling to java (ie kotlin) got rid of checked exceptions, and other languages like Rust use a Result which, which, with enough language constructs, can be quite good. While I agree that not having checked exceptions nor Result but instead only unchecked ones would be a bad idea, because it would mean that a part of the return type is unknown, which we wouldn't want in a strongly typed language, I believe some action needs to be taken. In my opinion, one of its burdens is caused by the try-catch language feature : String text; try { text = Files.readString(somePath); } catch(IOException ex) { throw new UncheckedIOException(ex); } //do something with text This is a common example, of how you would use it : you want to read a string, and an IO error shouldn't happen, so you fail-fast The reason why I didn't use text inside the catch, is because the catch should only be for this specific exception, and also it would add another level of nesting, which would start to hurt when other ifs or try-catches appear. This code is boilerplate and is error-prone, since you always have to repeat the same lines, it is also weird for any non java programmer. And this is far from being the worst, because another common example is with streams, since you can't throw checked exception, you have to handle each time, in each stream operation, and even if you wrap this code in methods and use method references, it's still far less readable than having a short lambda where you see exactly what the stream is doing. If checked exceptions were instead a Result type, a solution to this problem would be to make a unwrap() method (like in Rust, or like with Java's Optional#orElseThrow()) String text = Files.readString(somePath).unwrap(); So my suggestion is that, since catching is a language feature, it can only be dealt with another language feature : String text = Files.readString(somePath) throw IOException ex as new UncheckedIOException(ex); If this method throws an IOException, it will rethrow it as an UncheckedIOException. About the syntax, I used "throw" since it's an already used keyword, but depending of the meaning, it could be "catch" or whatever, and "as" could be "->" if using a contextual keyword is too much. This code could even be simplied as the following with new methods or language features, in the future : String text = Files.readString(somePath) throw IOException ex as ex.unchecked(); String text = Files.readString(somePath) throw IOException as unchecked; etc. Unchecked part could either be the same exception except the compiler ignores it (I know this is possible since it's possible to throw a checked exception as an unchecked), the idea is that since you are telling the compiler that if an exception happens, then it should fail-fast, then the compiler should be able to say "ok, I trust you". An alternative would be that the unchecked simply wrap it in a RuntimeException or a specific unchecked exception. So, what's the point of this, does it only serve this use case ? So first, is it important to note that it isn't just for checked -> fail-fast, but also checked -> some other checked, when exception conversion is needed, which can be useful, and also unchecked -> checked/unchecked, for example if an API only provides an unchecked like Integer#parseInt. Now about the use case : this syntax, isn't just a compressed try-catch, it's also an expression, which is very important, because not only you can very clearly handle this kind of cases, but it can provide easy to read, concise code in lambdas and assignments, for example : try (var files = Files.list(path) { return files.filter(this::matcher) .map(p -> Files.readString(p) throw IOException as unchecked)//Possible syntax Files::readString throw IOException as unchecked ? .toList(); } And in the future, a new method could be added for streams called .continueOnFailure(IOExcepion.class) (or UncheckedIOException if the unchecked wraps the exception instead of marking it as unchecked) for example, which would continue even if there is an exception. An additional syntax could also be provided for cases where catching is used as an if else : OptionalInt parseInt(String s) { return OptionalInt.of(Integer.parseInt(s)) catch NumberFormatException -> OptionalInt.empty(); // Using -> syntax here instead of "as" to show that it can also make sense } I hope it can fix this unholy war of checked exceptions which never seem to advance. Sincerely. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Sun Mar 5 20:16:26 2023 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sun, 5 Mar 2023 21:16:26 +0100 (CET) Subject: Language feature to improve checked exceptions In-Reply-To: <2CE5B2B1-F326-4C88-8A3D-654E45CAA692@oracle.com> References: <5b723c33-d38f-97f7-e5d4-ad7c345a74ff@oracle.com> <27180353.3948403.1678041449526.JavaMail.zimbra@u-pem.fr> <2CE5B2B1-F326-4C88-8A3D-654E45CAA692@oracle.com> Message-ID: <2118612741.4060252.1678047386247.JavaMail.zimbra@u-pem.fr> > From: "Ron Pressler" > To: "Remi Forax" > Cc: "Brian Goetz" , "Tom" , > "amber-dev" > Sent: Sunday, March 5, 2023 8:37:41 PM > Subject: Re: Language feature to improve checked exceptions > Checked exceptions don?t intrinsically "colour your function? any more (or less) > than a return type. Java?s ability to helpfully polymorphise over checked > exceptions, however, is more limited. That's factually correct but it's like saying that C# Task or JS Promise don't intrinsically color your function because they are return types. Unchecked exceptions let you insert functions that are unaware of the exception in between the function that may raise an exception and the function that recover from that exception. So checked exceptions compared to unchecked exceptions color your function. R?mi > ? Ron >> On 5 Mar 2023, at 18:37, Remi Forax < [ mailto:forax at univ-mlv.fr | >> forax at univ-mlv.fr ] > wrote: >>> From: "Brian Goetz" < [ mailto:brian.goetz at oracle.com | brian.goetz at oracle.com ] >>> > >>> To: "Tom L" < [ mailto:tom_l64 at hotmail.com | tom_l64 at hotmail.com ] >, >>> "amber-dev" < [ mailto:amber-dev at openjdk.org | amber-dev at openjdk.org ] > >>> Sent: Sunday, March 5, 2023 3:24:38 AM >>> Subject: Re: Language feature to improve checked exceptions >>> I have some sympathy for the desire to optimize catch-and-wrap; doing this the >>> regular way is indeed syntactically painful when the actual business logic is >>> small (which it often is.) Catch-and-wrap works well as an abstraction idiom >>> when turning a low-level exception (e.g., IOException) into a higher-level one >>> (e.g., XMLException); as higher-level library-code delegates to lower-level >>> library code, it will want to remap low-level errors to higher-level ones. >>> (Particularly interesting is catch-and-wrap at the method declaration level, >>> with some sort of `throws X as Y`, as it represents the catch-and-wrap rules >>> declaratively rather than imperatively.) >>> However, the examples you give of catch-and-wrap are not really catch-and-wrap; >>> they are catch-and-pretend-they-didn't-happen (by turning checked exceptions >>> into unchecked ones.) While this would surely be popular among the "checked >>> exceptions suck" crowd, this is not making error handling more reliable, it is >>> just making it easier to ignore errors. >> A remark, a catch-and-pretend-they-didn't-happen is just a catch-and-wrap >> without the enclosing context. >> For example, you can see >> List foo() { >> ... >> List list = stream.map(it -> { >> try { >> return IO.something(it); >> } catch(IOException e) { >> throw new UncheckedIOException(e); >> } >> }).toList(), >> return list; >> } >> but foo() is called like this >> List list; >> try { >> list = foo() >> } catch(UncheckedIOException e) { >> throw e.getCause(); >> } >>> The Result approach is both safe and honest (and can be implemented today >>> without language help), but is more foreign to Java developers. It operates by >>> moving the side-channel result into the main channel, which enables monadic >>> composition (turning a partial operation into a total one returning a >>> Success|Fail union). With pattern matching in the language, this gets even more >>> attractive. Still, I suspect that most of the "checked exceptions suck" crowd >>> wouldn't thank us for kicking off the massive, decade-long migration from >>> checked exceptions to the Either monad. >> From Java the language POV, returning Either or using a checked exception both >> side of the same coin, there are semantically equivalent. >> As a proud member of the "checked exception suck in Java" crowd. The "in Java" >> is important. Checked exceptions do not suck in the vacuum, it suck in Java >> because on a higher level is goes against the freedom of composition and >> because on a lower level the Java type system does not handle them well, no >> union type (only precise rethrow), no proper way to bundle exceptions into a >> type parameter. >> It's the colored function problem [1]. Checked exceptions aka the Result monad >> does not suck in Rust. Because inherently Rust is a language that consider >> library composition less important than precise lifetime tracking or >> asynchronous calls tracking, the whole language relies on colored functions. >> But Java is not Rust, freedom of composition, freedom to use any libraries >> whenever it was written, is an important part of the language. That's why Java >> has a GC, lightweight threads, lambdas, erased generics or the concept of >> binary backward compatibility. All those features enable free easy composition. >> Sadly checked exception in Java colors functions, they create an unnecessary >> artificial barrier to library compositions. That's why they suck in Java. >> regards, >> R?mi >> [1] [ https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ >> | https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ ] >>> On 3/4/2023 6:49 PM, Tom L wrote: >>>> Hello, it's the first time I send an email here, I have no idea how things work, >>>> so I hope I am doing things somewhat correctly. >>>> I wanted to make a suggestion about exceptions : >>>> Checked exceptions are used as a meant of a second return type, which shouldn't >>>> happen in most cases, and then, depending of what the caller wants to do, it >>>> will handle it in a certain way. So in some sort, a checked exception is like >>>> part of the return type, like you would have a Result, >>>> compared to unchecked exception in java which are just supposed to be bugs. >>>> But this feature causes some burden, so much so that some languages, even >>>> languages compiling to java (ie kotlin) got rid of checked exceptions, and >>>> other languages like Rust use a Result which, which, with >>>> enough language constructs, can be quite good. >>>> While I agree that not having checked exceptions nor Result but instead only >>>> unchecked ones would be a bad idea, because it would mean that a part of the >>>> return type is unknown, which we wouldn't want in a strongly typed language, I >>>> believe some action needs to be taken. >>>> In my opinion, one of its burdens is caused by the try-catch language feature : >>>> String text; >>>> try { >>>> text = Files.readString(somePath); >>>> } catch(IOException ex) { >>>> throw new UncheckedIOException(ex); >>>> } >>>> //do something with text >>>> This is a common example, of how you would use it : you want to read a string, >>>> and an IO error shouldn't happen, so you fail-fast >>>> The reason why I didn't use text inside the catch, is because the catch should >>>> only be for this specific exception, and also it would add another level of >>>> nesting, which would start to hurt when other ifs or try-catches appear. >>>> This code is boilerplate and is error-prone, since you always have to repeat the >>>> same lines, it is also weird for any non java programmer. >>>> And this is far from being the worst, because another common example is with >>>> streams, since you can't throw checked exception, you have to handle each time, >>>> in each stream operation, and even if you wrap this code in methods and use >>>> method references, it's still far less readable than having a short lambda >>>> where you see exactly what the stream is doing. >>>> If checked exceptions were instead a Result type, a solution to this problem >>>> would be to make a unwrap() method (like in Rust, or like with Java's >>>> Optional#orElseThrow()) >>>> String text = Files.readString(somePath).unwrap(); >>>> So my suggestion is that, since catching is a language feature, it can only be >>>> dealt with another language feature : >>>> String text = Files.readString(somePath) throw IOException ex as new >>>> UncheckedIOException(ex); >>>> If this method throws an IOException, it will rethrow it as an >>>> UncheckedIOException. >>>> About the syntax, I used "throw" since it's an already used keyword, but >>>> depending of the meaning, it could be "catch" or whatever, and "as" could be >>>> "->" if using a contextual keyword is too much. >>>> This code could even be simplied as the following with new methods or language >>>> features, in the future : >>>> String text = Files.readString(somePath) throw IOException ex as ex.unchecked(); >>>> String text = Files.readString(somePath) throw IOException as unchecked; >>>> etc. >>>> Unchecked part could either be the same exception except the compiler ignores it >>>> (I know this is possible since it's possible to throw a checked exception as an >>>> unchecked), the idea is that since you are telling the compiler that if an >>>> exception happens, then it should fail-fast, then the compiler should be able >>>> to say "ok, I trust you". >>>> An alternative would be that the unchecked simply wrap it in a RuntimeException >>>> or a specific unchecked exception. >>>> So, what's the point of this, does it only serve this use case ? >>>> So first, is it important to note that it isn't just for checked -> fail-fast, >>>> but also checked -> some other checked, when exception conversion is needed, >>>> which can be useful, and also unchecked -> checked/unchecked, for example if an >>>> API only provides an unchecked like Integer#parseInt. >>>> Now about the use case : this syntax, isn't just a compressed try-catch, it's >>>> also an expression, which is very important, because not only you can very >>>> clearly handle this kind of cases, but it can provide easy to read, concise >>>> code in lambdas and assignments, for example : >>>> try (var files = Files.list(path) { >>>> return files.filter(this::matcher) >>>> .map(p -> Files.readString(p) throw IOException as unchecked)//Possible syntax >>>> Files::readString throw IOException as unchecked ? >>>> .toList(); >>>> } >>>> And in the future, a new method could be added for streams called >>>> .continueOnFailure(IOExcepion.class) (or UncheckedIOException if the unchecked >>>> wraps the exception instead of marking it as unchecked) for example, which >>>> would continue even if there is an exception. >>>> An additional syntax could also be provided for cases where catching is used as >>>> an if else : >>>> OptionalInt parseInt(String s) { >>>> return OptionalInt.of(Integer.parseInt(s)) catch NumberFormatException -> >>>> OptionalInt.empty(); // Using -> syntax here instead of "as" to show that it >>>> can also make sense >>>> } >>>> I hope it can fix this unholy war of checked exceptions which never seem to >>>> advance. >>>> Sincerely. -------------- next part -------------- An HTML attachment was scrubbed... URL: From duke at openjdk.org Tue Mar 7 13:22:38 2023 From: duke at openjdk.org (duke) Date: Tue, 7 Mar 2023 13:22:38 GMT Subject: git: openjdk/amber: created branch patterns-instanceof-primitive based on the branch patterns-match containing 0 unique commits Message-ID: <5a32b781-fe5a-40d1-b448-a6447561d222@openjdk.org> The message from this sender included one or more files which could not be scanned for virus detection; do not open these files unless you are certain of the sender's intent. ---------------------------------------------------------------------- The new branch patterns-instanceof-primitive is currently identical to the patterns-match branch. From duke at openjdk.org Tue Mar 7 13:22:50 2023 From: duke at openjdk.org (duke) Date: Tue, 7 Mar 2023 13:22:50 GMT Subject: git: openjdk/amber: created branch patterns-match based on the branch patterns-instanceof-primitive containing 0 unique commits Message-ID: <69c971cd-4767-4860-beb4-e7b992ebe7cf@openjdk.org> The new branch patterns-match is currently identical to the patterns-instanceof-primitive branch. From holo3146 at gmail.com Tue Mar 7 13:24:43 2023 From: holo3146 at gmail.com (Holo The Sage Wolf) Date: Tue, 7 Mar 2023 15:24:43 +0200 Subject: Language feature to improve checked exceptions Message-ID: This is a continuation of this thread , unfortunately I only joined the mailing list, so I can?t reply to it so I apologize for opening a new thread. I want to point to Koka , Koka is a language built upon the concept of Effect, a generalization of how Java treats checked exceptions (and similarly to what Brian Goetz said about how Checked exceptions are equivalent to Either, general Effect system is equivalent to full monadic type system). The language is very interesting and worth reading about, but the relevant part is their Polymorphic Effects, which allow you to extend the effects. In Java pseudo code, it means that the following is valid: @FunctionalInterfacepublic interface EPredicate { boolean test(T t) throws E; } @FunctionalInterfacepublic interface EConsumer { boolean test(T t) throws E; } public class EStream { public EStream filter(EPredicate predicate) { ... } public void forEach(EConsumer action) throws E, EX { ... } } Which allows you to have a fluent composition without losing the fine-grained checked exceptions: public static void main(string[] args) throws FileNotFoundException, IllegalAccessException, SQLException { myEStream.filter(v -> ...) // something that throws FileNotFoundException .filter(v -> ...) // something that throws IllegalAccessException .forEach(v -> ...); // something that throws SQLException } This also makes the type system of the throws clause a subset of the generics type system. While I do believe that the actual solution will require much more thought than this (e.g. deconstruction of generic sums to be able to handle specific exceptions and remove those exceptions from the final union type of exceptions), this is a showcase that a nice and composable checked exceptions works even without Monadic types. I find this direction much nicer than using Either everywhere. -- Holo The Wise Wolf Of Yoitsu -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Tue Mar 7 14:56:44 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 7 Mar 2023 09:56:44 -0500 Subject: Language feature to improve checked exceptions In-Reply-To: References: Message-ID: <5be57484-bb8a-00e0-e8fe-7a9a9ce82f2f@oracle.com> Thanks for the link to Koka. Indeed, I spent many hours prototyping something exactly like this for Streams back in the Java 8 days, as an extension to one of the "exception transparency" proposals that came up at the time.? As you point out, in libraries like streams, a throwing lambda passed as a parameter "pollutes" the type of the returned stream, since the lambda is incorporated into the stream and may be invoked by a later operation, and therefore might be thrown from that later operation. This basically asks the generic type system to have type variables to track effects as well as types (see, e.g., "Type and Effect Systems", Nielsen and Nielsen.) As your example shows, it is possible to do this with (a) some form of variadic generics and (b) some algebraic operations on variadic parameters (e.g., concatenating, unioning, differencing).? It also means that generic classes like Stream have to carry around additional type variables describing their exceptions (effects), and those tvars are "different" from ordinary tvars. Our conclusion at the time we did this experiment is that, while the approach is viable, annotation burden on library authors is high, and this burden flows through to clients as well.? (Even if we allow use sites to elide the exception information, so they can say Stream rather than Stream, it still flows into the docs and error messages.)? It also makes generics "even more complex" (and people still complain generics are too complex.) So the outcome of this experiment was "yes, it can be made to work, but no, we don't think putting into Java at this time is likely to be seen entirely positively." > While I do believe that the actual solution will require much more > thought than this (e.g. deconstruction of generic sums to be able to > handle specific exceptions and remove those exceptions from the final > union type of exceptions), this is a showcase that a nice and > composable checked exceptions works even without Monadic types. > Indeed. The need for asymmetric difference as one of the algebraic operations on effect variables is one of those surprises that bites you the first time you encounter it. > I find this direction much nicer than using Either everywhere. > I am not sure whether I do or not, which is one reason we've not done either yet... On 3/7/2023 8:24 AM, Holo The Sage Wolf wrote: > > This is a continuation of this thread > , > unfortunately I only joined the mailing list, so I can?t reply to it > so I apologize for opening a new thread. > > I want to point to Koka > , Koka is > a language built upon the concept of Effect, a generalization of how > Java treats checked exceptions (and similarly to what Brian Goetz said > about how Checked exceptions are equivalent to Either, general Effect > system is equivalent to full monadic type system). > > The language is very interesting and worth reading about, but the > relevant part is their Polymorphic Effects, which allow you to extend > the effects. In Java pseudo code, it means that the following is valid: > > |@FunctionalInterface public interface EPredicate Throwable> { boolean test(T t) throws E; } @FunctionalInterface public > interface EConsumer { boolean test(T t) throws > E; } public class EStream { public Throwable> EStream filter(EPredicate > predicate) { ... } public void > forEach(EConsumer action) throws E, EX { ... } } | > > Which allows you to have a fluent composition without losing the > fine-grained checked exceptions: > > |public static void main(string[] args) throws FileNotFoundException, > IllegalAccessException, SQLException { myEStream.filter(v -> ...) // > something that throws FileNotFoundException .filter(v -> ...) // > something that throws IllegalAccessException .forEach(v -> ...); // > something that throws SQLException } | > > This also makes the type system of the |throws| clause a subset of the > generics type system. > > While I do believe that the actual solution will require much more > thought than this (e.g. deconstruction of generic sums to be able to > handle specific exceptions and remove those exceptions from the final > union type of exceptions), this is a showcase that a nice and > composable checked exceptions works even without Monadic types. > > I find this direction much nicer than using Either everywhere. > > -- > Holo The Wise Wolf Of Yoitsu -------------- next part -------------- An HTML attachment was scrubbed... URL: From holo3146 at gmail.com Tue Mar 7 16:05:59 2023 From: holo3146 at gmail.com (Holo The Sage Wolf) Date: Tue, 7 Mar 2023 18:05:59 +0200 Subject: Language feature to improve checked exceptions In-Reply-To: <5be57484-bb8a-00e0-e8fe-7a9a9ce82f2f@oracle.com> References: <5be57484-bb8a-00e0-e8fe-7a9a9ce82f2f@oracle.com> Message-ID: I do want to write for the record my reasons to prefer the Effect system approach in the hope to convince you and others about my view, but I do acknowledge that this discussion is still far, and the relevance of my words now may only appear when the work on patterns and string templates will finalize, so unless there is a good reason I will not continue the discussion further after this message. Effect System enables a code style that is easier to follow, it hides the "unimportant" parts (unimportant to the logic) somewhere in the background and allows you to write as if there are no effects to your code (I am aware of the irony here). Of course it does in the end force you to handle the effects, but you can handle those effects orthogonally to your actual logic. Unlike the Monadic system which requires you to work in "the monad world" as described a lot by Haskell developers, you need to carry your Either wraps everywhere till you handle them. In addition, I believe that there is another argument for the Effect system specifically for Java: there is a difference between a language feature and a datatype, unlike some other languages Java is built heavily over 3rd party libraries, if we look at the big libraries out there we would find that a lot of them are using custom Either (and friends) classes, this cause that composing those libraries is not as simple as we would want it to be, by making a language feature that handle those cases *well*, there will be a much larger intersection between the APIs (the "*well"* in the previous sentence is important, I want to believe that it is possible to do that in Java) Finally I want to link 2 more projects. Apart from Koka there is also [Effekt](https://effekt-lang.org/), I like the way Koka handles things better, but Effekt is another language that has Effects as its core mechanic. Apart from Effect systems, there is also the idea of Coeffect systems, for a similar reasons as above, I really like a Coeffect type system and want to link to [my coeffect system in Java](https://github.com/Holo314/Coeffect) (I didn't have much time to work on it lately, but currently I am fighting Java's compiler plugins API to make it a stand alone plugin, as well as in the process of implementing custom type system in the annotation to enable stuff similar to what I wrote in my previous mail). Again, I would love to discuss further when it is more relevant. On Tue, Mar 7, 2023 at 4:56?PM Brian Goetz wrote: > Thanks for the link to Koka. > > Indeed, I spent many hours prototyping something exactly like this for > Streams back in the Java 8 days, as an extension to one of the "exception > transparency" proposals that came up at the time. As you point out, in > libraries like streams, a throwing lambda passed as a parameter "pollutes" > the type of the returned stream, since the lambda is incorporated into the > stream and may be invoked by a later operation, and therefore might be > thrown from that later operation. > > This basically asks the generic type system to have type variables to > track effects as well as types (see, e.g., "Type and Effect Systems", > Nielsen and Nielsen.) > > As your example shows, it is possible to do this with (a) some form of > variadic generics and (b) some algebraic operations on variadic parameters > (e.g., concatenating, unioning, differencing). It also means that generic > classes like Stream have to carry around additional type variables > describing their exceptions (effects), and those tvars are "different" from > ordinary tvars. > > Our conclusion at the time we did this experiment is that, while the > approach is viable, annotation burden on library authors is high, and this > burden flows through to clients as well. (Even if we allow use sites to > elide the exception information, so they can say Stream rather than > Stream, it still flows into the docs and error > messages.) It also makes generics "even more complex" (and people still > complain generics are too complex.) > > So the outcome of this experiment was "yes, it can be made to work, but > no, we don't think putting into Java at this time is likely to be seen > entirely positively." > > While I do believe that the actual solution will require much more thought > than this (e.g. deconstruction of generic sums to be able to handle > specific exceptions and remove those exceptions from the final union type > of exceptions), this is a showcase that a nice and composable checked > exceptions works even without Monadic types. > > > Indeed. The need for asymmetric difference as one of the algebraic > operations on effect variables is one of those surprises that bites you the > first time you encounter it. > > I find this direction much nicer than using Either everywhere. > > > I am not sure whether I do or not, which is one reason we've not done > either yet... > > > > On 3/7/2023 8:24 AM, Holo The Sage Wolf wrote: > > This is a continuation of this thread > , > unfortunately I only joined the mailing list, so I can?t reply to it so I > apologize for opening a new thread. > > I want to point to Koka > , Koka is a > language built upon the concept of Effect, a generalization of how Java > treats checked exceptions (and similarly to what Brian Goetz said about how > Checked exceptions are equivalent to Either, general Effect system is > equivalent to full monadic type system). > > The language is very interesting and worth reading about, but the relevant > part is their Polymorphic Effects, which allow you to extend the effects. > In Java pseudo code, it means that the following is valid: > > @FunctionalInterfacepublic interface EPredicate { > boolean test(T t) throws E; > } > @FunctionalInterfacepublic interface EConsumer { > boolean test(T t) throws E; > } > public class EStream { > public EStream filter(EPredicate predicate) { > ... > } > > public void forEach(EConsumer action) throws E, EX { > ... > } > } > > Which allows you to have a fluent composition without losing the > fine-grained checked exceptions: > > public static void main(string[] args) throws FileNotFoundException, IllegalAccessException, SQLException { > myEStream.filter(v -> ...) // something that throws FileNotFoundException > .filter(v -> ...) // something that throws IllegalAccessException > .forEach(v -> ...); // something that throws SQLException > } > > This also makes the type system of the throws clause a subset of the > generics type system. > > While I do believe that the actual solution will require much more thought > than this (e.g. deconstruction of generic sums to be able to handle > specific exceptions and remove those exceptions from the final union type > of exceptions), this is a showcase that a nice and composable checked > exceptions works even without Monadic types. > > I find this direction much nicer than using Either everywhere. > -- > Holo The Wise Wolf Of Yoitsu > > > -- Holo The Wise Wolf Of Yoitsu -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Tue Mar 7 17:58:32 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 7 Mar 2023 12:58:32 -0500 Subject: Language feature to improve checked exceptions In-Reply-To: References: <5be57484-bb8a-00e0-e8fe-7a9a9ce82f2f@oracle.com> Message-ID: Thanks for the well-reasoned thoughts. Indeed, the monadic approach is more constraining and explicit (and has the accidental complexity of nominality, in that my Either won't interoperate with yours.)? Where I think we'd find differences of opinion is whether those constraints are a bug or a feature.? I agree that improving the ability to abstract over exception types is the more Java-like solution. In reality, though, people will want both.? A good analogy is Streams.? For the kinds of computations streams is good for -- and that's a lot -- people love it, it is clear, concise, reliable, and performant.? But it would be silly to say "let's get rid of all the control flow constructs in Java, and just do everything with streams" -- streams just isn't up to that task, and that's OK.? It's a flexible tool, and its good for what it is good for. Unsurprisingly, Either is good in the same ways -- for some kinds of constrained composition, you can just >>= your way along and express things clearly and safely.? But just as we don't want to do all our computation with streams, we won't want to do all our computation this way either, and will want to punch out to a more imperative mechanism. Finally, I'll point out that Streams, because of its inherent constraints, would work fine in either world, so we can't extrapolate very much from how it would work there. Cheers, -Brian On 3/7/2023 11:05 AM, Holo The Sage Wolf wrote: > I do want to write for the record my reasons to prefer the Effect > system approach in the hope to convince?you and others about my view, > but I do acknowledge that this discussion is still far, and?the > relevance of my words now may only appear when the work on patterns > and string templates will finalize, so unless there is a good reason I > will not continue the discussion further after this message. > > Effect System enables a code style that is easier to follow, it hides > the "unimportant" parts (unimportant to the logic) somewhere in the > background and allows you to write as if there are no effects to your > code (I am aware of the irony here). Of course it does in the end > force you to handle the effects, but you can handle those effects > orthogonally to your actual logic. Unlike the Monadic system which > requires you to work in "the monad world" as described a lot by > Haskell developers, you need to carry your Either wraps everywhere > till you handle them. > > In addition, I believe that there is another argument for the Effect > system specifically for Java: there is a difference between a language > feature and a datatype, unlike some other languages Java is built > heavily?over 3rd party libraries, if we look at the big libraries out > there we would find that a lot of them are using custom Either (and > friends) classes, this cause that composing those libraries is not as > simple as we would want it to be, by making a language feature that > handle those cases /well/, there will be a much larger intersection > between the APIs (the "/well"/? in the previous sentence is important, > I want to believe that it is possible to do that in Java) > > Finally I want to link 2 more projects. > > Apart from Koka there is also [Effekt](https://effekt-lang.org/ > ), > I like the way Koka handles things better, but Effekt is another > language that has Effects as its core mechanic. > > Apart from Effect systems, there is also the idea of Coeffect systems, > for a similar reasons as above, I really like a Coeffect type system > and want to link to [my coeffect system in > Java](https://github.com/Holo314/Coeffect > ) > (I didn't have much time to work on it lately, but currently I am > fighting Java's compiler plugins API to make it a stand alone plugin, > as well as in the process of implementing custom type system in the > annotation to enable stuff similar to what I wrote in my previous mail). > > Again, I would love to discuss further when it is more relevant. > > On Tue, Mar 7, 2023 at 4:56?PM Brian Goetz wrote: > > Thanks for the link to Koka. > > Indeed, I spent many hours prototyping something exactly like this > for Streams back in the Java 8 days, as an extension to one of the > "exception transparency" proposals that came up at the time.? As > you point out, in libraries like streams, a throwing lambda passed > as a parameter "pollutes" the type of the returned stream, since > the lambda is incorporated into the stream and may be invoked by a > later operation, and therefore might be thrown from that later > operation. > > This basically asks the generic type system to have type variables > to track effects as well as types (see, e.g., "Type and Effect > Systems", Nielsen and Nielsen.) > > As your example shows, it is possible to do this with (a) some > form of variadic generics and (b) some algebraic operations on > variadic parameters (e.g., concatenating, unioning, > differencing).? It also means that generic classes like Stream > have to carry around additional type variables describing their > exceptions (effects), and those tvars are "different" from > ordinary tvars. > > Our conclusion at the time we did this experiment is that, while > the approach is viable, annotation burden on library authors is > high, and this burden flows through to clients as well.? (Even if > we allow use sites to elide the exception information, so they can > say Stream rather than Stream, it > still flows into the docs and error messages.)? It also makes > generics "even more complex" (and people still complain generics > are too complex.) > > So the outcome of this experiment was "yes, it can be made to > work, but no, we don't think putting into Java at this time is > likely to be seen entirely positively." > >> While I do believe that the actual solution will require much >> more thought than this (e.g. deconstruction of generic sums to be >> able to handle specific exceptions and remove those exceptions >> from the final union type of exceptions), this is a showcase that >> a nice and composable checked exceptions works even without >> Monadic types. >> > > Indeed. The need for asymmetric difference as one of the algebraic > operations on effect variables is one of those surprises that > bites you the first time you encounter it. > >> I find this direction much nicer than using Either everywhere. >> > > I am not sure whether I do or not, which is one reason we've not > done either yet... > > > > On 3/7/2023 8:24 AM, Holo The Sage Wolf wrote: >> >> This is a continuation of this thread >> , >> unfortunately I only joined the mailing list, so I can?t reply to >> it so I apologize for opening a new thread. >> >> I want to point to Koka >> , >> Koka is a language built upon the concept of Effect, a >> generalization of how Java treats checked exceptions (and >> similarly to what Brian Goetz said about how Checked exceptions >> are equivalent to Either, general Effect system is equivalent to >> full monadic type system). >> >> The language is very interesting and worth reading about, but the >> relevant part is their Polymorphic Effects, which allow you to >> extend the effects. In Java pseudo code, it means that the >> following is valid: >> >> |@FunctionalInterface public interface EPredicate> Throwable> { boolean test(T t) throws E; } @FunctionalInterface >> public interface EConsumer { boolean >> test(T t) throws E; } public class EStream >> { public EStream >> filter(EPredicate predicate) { ... } public >> void forEach(EConsumer >> action) throws E, EX { ... } } | >> >> Which allows you to have a fluent composition without losing the >> fine-grained checked exceptions: >> >> |public static void main(string[] args) throws >> FileNotFoundException, IllegalAccessException, SQLException { >> myEStream.filter(v -> ...) // something that throws >> FileNotFoundException .filter(v -> ...) // something that throws >> IllegalAccessException .forEach(v -> ...); // something that >> throws SQLException } | >> >> This also makes the type system of the |throws| clause a subset >> of the generics type system. >> >> While I do believe that the actual solution will require much >> more thought than this (e.g. deconstruction of generic sums to be >> able to handle specific exceptions and remove those exceptions >> from the final union type of exceptions), this is a showcase that >> a nice and composable checked exceptions works even without >> Monadic types. >> >> I find this direction much nicer than using Either everywhere. >> >> -- >> Holo The Wise Wolf Of Yoitsu > > > > -- > Holo The Wise Wolf Of Yoitsu -------------- next part -------------- An HTML attachment was scrubbed... URL: From abimpoudis at openjdk.org Wed Mar 8 09:56:37 2023 From: abimpoudis at openjdk.org (Aggelos Biboudis) Date: Wed, 8 Mar 2023 09:56:37 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch Message-ID: Prototype implementation for primitive types in patterns, instanceof, and switch. draft JEP: https://openjdk.org/jeps/8288476 draft spec: https://cr.openjdk.org/~abimpoudis/instanceof/latest/ ------------- Commit messages: - Remove whitespaces again - Remove whitespaces - Fix bootstrap - Merge remote-tracking branch 'upstream/master' into instanceof - Merge remote-tracking branch 'upstream/master' into instanceof - Adjust processCases and move the unconditionally exact test to Types - Merge remote-tracking branch 'upstream/master' into instanceof - Add failing test - Add test case - Merge remote-tracking branch 'upstream/master' into instanceof - ... and 8 more: https://git.openjdk.org/amber/compare/fb130639...dd8ca97e Changes: https://git.openjdk.org/amber/pull/91/files Webrev: https://webrevs.openjdk.org/?repo=amber&pr=91&range=00 Issue: https://bugs.openjdk.org/browse/JDK-8303374 Stats: 2137 lines in 36 files changed: 1873 ins; 123 del; 141 mod Patch: https://git.openjdk.org/amber/pull/91.diff Fetch: git fetch https://git.openjdk.org/amber pull/91/head:pull/91 PR: https://git.openjdk.org/amber/pull/91 From themrmilchmann at gmail.com Thu Mar 9 12:15:41 2023 From: themrmilchmann at gmail.com (Leon Linhart) Date: Thu, 9 Mar 2023 13:15:41 +0100 Subject: Unexpected use of "ConditionalExpression" in JLS grammars Message-ID: Hi, Initially, I believed to have found a mistake in the JLS (Java 19 Edition) that was introduced with JEP 361. However, while further looking into the spec, this has developed into more of a question than a report. In several places in the grammar where I'd expect to find `ConstantExpression`, `ConditionalExpression` is used instead. Specifically, in ?14.11.1 [1], the grammar for a `CaseConstant` is defined through a `ConditionalExpression`. I'd tend to think this should at least be a `ConstantExpression` instead as this would be more in line with the textual description of a case constant: > Every case constant must be either a constant expression (?15.29) or the name ? of an enum constant (?8.9.1), or a compile-time error occurs. Although, from a quick reading of the JLS, I'm not sure if `ConstantExpression` covers names of enum constants. However, neither does the current definition. Interestingly, the same production is used for the definition of `ElementValue` for annotations in ?9.7.1 [2], which seems similarly out-of-place to me. My opinion is further reinforced by the fact that the following snippet compiles despite using a constant expression for a case label that is not a conditional expression: ?? ?public static final int BASE = 42; ??? public void printForBasePlusOne(int i) { ??????? switch (i) { ??????????? case BASE + 1 -> System.out.println("Test"); ?? ??? ??? ?default -> {} ??????? } ??? } Now, I'm curious about the reasoning behind using `ConditionalExpression` in these places in the JLS and if this might actually be a mistake. If this is not the right place to ask about this, please point me in the right direction. Regards Leon Linhart [1] https://docs.oracle.com/javase/specs/jls/se19/html/jls-14.html#jls-14.11.1 [2] https://docs.oracle.com/javase/specs/jls/se19/html/jls-9.html#jls-9.7.1 From alex.buckley at oracle.com Thu Mar 9 17:45:20 2023 From: alex.buckley at oracle.com (Alex Buckley) Date: Thu, 9 Mar 2023 09:45:20 -0800 Subject: Unexpected use of "ConditionalExpression" in JLS grammars In-Reply-To: References: Message-ID: <203c5b17-61a0-05d5-f38c-b68ee202bd94@oracle.com> This is a good and interesting question. The ConstantExpression production in JLS 15.29 uses the Expression nonterminal. This is good for giving a lightning-fast intro to the topic, but also bad because Expression is extremely general. Expression allows LambdaExpression and AssignmentExpression, neither of which are usually desirable in constant-like places. Thus, 15.29 does not define "constant expression" syntactically as "Anything that matches ConstantExpression". Rather, 15.29 defines it _semantically_, as an expression that meets various criteria. ConditionalExpression is the highest nonterminal under Expression that syntactically precludes lambda expressions and assignment expressions. This makes ConditionalExpression the "go to" nonterminal in many places, as it lets wholly invalid code be ruled out _syntactically_, during parsing, early on in compilation. This is not some artifact of compiler implementation, but rather an artifact of how the Java language generally prefers to reject invalid code sooner rather than later. We still need to _semantically_ constrain the conditional expression to be a "constant expression" (or else compile-time error), but we would have to do that even if using ConstantExpression instead of ConditionalExpression. (`BASE + 1` is an AdditiveExpression, so a ConditionalExpression, and an Expression, and a ConstantExpression.) Alex On 3/9/2023 4:15 AM, Leon Linhart wrote: > Hi, > Initially, I believed to have found a mistake in the JLS (Java 19 > Edition) that > was introduced with JEP 361. However, while further looking into the > spec, this > has developed into more of a question than a report. > > In several places in the grammar where I'd expect to find > `ConstantExpression`, > `ConditionalExpression` is used instead. Specifically, in ?14.11.1 [1], the > grammar for a `CaseConstant` is defined through a > `ConditionalExpression`. I'd > tend to think this should at least be a `ConstantExpression` instead as > this > would be more in line with the textual description of a case constant: > > > Every case constant must be either a constant expression (?15.29) or > the name > ? of an enum constant (?8.9.1), or a compile-time error occurs. > > Although, from a quick reading of the JLS, I'm not sure if > `ConstantExpression` > covers names of enum constants. However, neither does the current > definition. > Interestingly, the same production is used for the definition of > `ElementValue` > for annotations in ?9.7.1 [2], which seems similarly out-of-place to me. > > My opinion is further reinforced by the fact that the following snippet > compiles despite using a constant expression for a case label that is not a > conditional expression: > > ?? ?public static final int BASE = 42; > > ??? public void printForBasePlusOne(int i) { > ??????? switch (i) { > ??????????? case BASE + 1 -> System.out.println("Test"); > ?? ??? ??? ?default -> {} > ??????? } > ??? } > > Now, I'm curious about the reasoning behind using > `ConditionalExpression` in > these places in the JLS and if this might actually be a mistake. If this > is not > the right place to ask about this, please point me in the right direction. > > > Regards > Leon Linhart > > > [1] > https://docs.oracle.com/javase/specs/jls/se19/html/jls-14.html#jls-14.11.1 > [2] https://docs.oracle.com/javase/specs/jls/se19/html/jls-9.html#jls-9.7.1 From duke at openjdk.org Fri Mar 10 00:04:43 2023 From: duke at openjdk.org (Michael Hixson) Date: Fri, 10 Mar 2023 00:04:43 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch In-Reply-To: References: Message-ID: <5RwFlDuN7k_aoTPAwZP8kS0lIum9-sPAyLqj9ivyOlU=.85872b6d-0303-49a8-a02d-7f3697a1c1c2@github.com> On Tue, 7 Mar 2023 13:23:29 GMT, Aggelos Biboudis wrote: > Prototype implementation for primitive types in patterns, instanceof, and switch. > > draft JEP: https://openjdk.org/jeps/8288476 > > draft spec: https://cr.openjdk.org/~abimpoudis/instanceof/latest/ src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 178: > 176: (64 - (Long.numberOfLeadingZeros(n) + > 177: Long.numberOfTrailingZeros(n))) ; > 178: } Are you interested in optimizing the performance of these methods at this time? For example, in this method: * You can remove the `Long.MIN_VALUE` check. That value "accidentally" works out with the rest of the logic; it has no leading zeros and 63 trailing zeros. This optimization assumes that `Long.MIN_VALUE` isn't common enough to deserve its own fast path (at the expense of every other input), which seems true. * If you assume that most inputs to this method are "small" integers that do fit in doubles (which I think is true), you can optimize for them by checking trailing zeros lazily. ```java n = Math.abs(n); int z = Long.numberOfLeadingZeros(n); return z > 10 || z + Long.numberOfTrailingZeros(n) > 10; ``` I can share benchmarks if that's useful. (I needed this method years ago, and I wrote benchmarks back then.) I'm assuming these methods are invoked whenever an expression like `longValue instanceof double` is evaluated, and that these methods aren't intrinsified (yet), so they seem worth optimizing. But I could be mistaken. ------------- PR: https://git.openjdk.org/amber/pull/91 From abimpoudis at openjdk.org Fri Mar 10 10:43:54 2023 From: abimpoudis at openjdk.org (Aggelos Biboudis) Date: Fri, 10 Mar 2023 10:43:54 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch [v2] In-Reply-To: References: Message-ID: > Prototype implementation for primitive types in patterns, instanceof, and switch. > > draft JEP: https://openjdk.org/jeps/8288476 > > draft spec: https://cr.openjdk.org/~abimpoudis/instanceof/latest/ Aggelos Biboudis has updated the pull request incrementally with one additional commit since the last revision: Update int_float, long_float, long_double Co-authored-by: Angelos Bimpoudis Co-authored-by: Raffaello Giulietti ------------- Changes: - all: https://git.openjdk.org/amber/pull/91/files - new: https://git.openjdk.org/amber/pull/91/files/dd8ca97e..80380474 Webrevs: - full: https://webrevs.openjdk.org/?repo=amber&pr=91&range=01 - incr: https://webrevs.openjdk.org/?repo=amber&pr=91&range=00-01 Stats: 20 lines in 1 file changed: 0 ins; 17 del; 3 mod Patch: https://git.openjdk.org/amber/pull/91.diff Fetch: git fetch https://git.openjdk.org/amber pull/91/head:pull/91 PR: https://git.openjdk.org/amber/pull/91 From abimpoudis at openjdk.org Fri Mar 10 10:43:57 2023 From: abimpoudis at openjdk.org (Aggelos Biboudis) Date: Fri, 10 Mar 2023 10:43:57 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch [v2] In-Reply-To: <5RwFlDuN7k_aoTPAwZP8kS0lIum9-sPAyLqj9ivyOlU=.85872b6d-0303-49a8-a02d-7f3697a1c1c2@github.com> References: <5RwFlDuN7k_aoTPAwZP8kS0lIum9-sPAyLqj9ivyOlU=.85872b6d-0303-49a8-a02d-7f3697a1c1c2@github.com> Message-ID: On Fri, 10 Mar 2023 00:01:28 GMT, Michael Hixson wrote: >> Aggelos Biboudis has updated the pull request incrementally with one additional commit since the last revision: >> >> Update int_float, long_float, long_double >> >> Co-authored-by: Angelos Bimpoudis >> Co-authored-by: Raffaello Giulietti > > src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 178: > >> 176: (64 - (Long.numberOfLeadingZeros(n) + >> 177: Long.numberOfTrailingZeros(n))) ; >> 178: } > > Are you interested in optimizing the performance of these methods at this time? > > For example, in this method: > * You can remove the `Long.MIN_VALUE` check. That value "accidentally" works out with the rest of the logic; it has no leading zeros and 63 trailing zeros. This optimization assumes that `Long.MIN_VALUE` isn't common enough to deserve its own fast path (at the expense of every other input), which seems true. > * If you assume that most inputs to this method are "small" integers that do fit in doubles (which I think is true), you can optimize for them by checking trailing zeros lazily. > ```java > n = Math.abs(n); > int z = Long.numberOfLeadingZeros(n); > return z > 10 || z + Long.numberOfTrailingZeros(n) > 10; > ``` > > I can share benchmarks if that's useful. (I needed this method years ago, and I wrote benchmarks back then.) > > I'm assuming these methods are invoked whenever an expression like `longValue instanceof double` is evaluated, and that these methods aren't intrinsified (yet), so they seem worth optimizing. But I could be mistaken. Thanks for leaving a comment @michaelhixson. The final implementation could contain more compact versions like `n == (long)(double)n && n != Long.MAX_VALUE;`. This prototype on the amber repo is to assess correctness for now. But indeed we can proceed by putting the less readable but more performant versions we have for some methods (@rgiulietti). You are right, these methods are not intrinsified. ------------- PR: https://git.openjdk.org/amber/pull/91 From rgiulietti at openjdk.org Fri Mar 10 13:59:58 2023 From: rgiulietti at openjdk.org (Raffaello Giulietti) Date: Fri, 10 Mar 2023 13:59:58 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch [v2] In-Reply-To: <5RwFlDuN7k_aoTPAwZP8kS0lIum9-sPAyLqj9ivyOlU=.85872b6d-0303-49a8-a02d-7f3697a1c1c2@github.com> References: <5RwFlDuN7k_aoTPAwZP8kS0lIum9-sPAyLqj9ivyOlU=.85872b6d-0303-49a8-a02d-7f3697a1c1c2@github.com> Message-ID: On Fri, 10 Mar 2023 00:01:28 GMT, Michael Hixson wrote: >> Aggelos Biboudis has updated the pull request incrementally with one additional commit since the last revision: >> >> Update int_float, long_float, long_double >> >> Co-authored-by: Angelos Bimpoudis >> Co-authored-by: Raffaello Giulietti > > src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 178: > >> 176: (64 - (Long.numberOfLeadingZeros(n) + >> 177: Long.numberOfTrailingZeros(n))) ; >> 178: } > > Are you interested in optimizing the performance of these methods at this time? > > For example, in this method: > * You can remove the `Long.MIN_VALUE` check. That value "accidentally" works out with the rest of the logic; it has no leading zeros and 63 trailing zeros. This optimization assumes that `Long.MIN_VALUE` isn't common enough to deserve its own fast path (at the expense of every other input), which seems true. > * If you assume that most inputs to this method are "small" integers that do fit in doubles (which I think is true), you can optimize for them by checking trailing zeros lazily. > ```java > n = Math.abs(n); > int z = Long.numberOfLeadingZeros(n); > return z > 10 || z + Long.numberOfTrailingZeros(n) > 10; > ``` > > I can share benchmarks if that's useful. (I needed this method years ago, and I wrote benchmarks back then.) > > I'm assuming these methods are invoked whenever an expression like `longValue instanceof double` is evaluated, and that these methods aren't intrinsified (yet), so they seem worth optimizing. But I could be mistaken. @michaelhixson The more compact variant given above in the comment by @biboudis, compiles to minimal machine code. There's hardly any need for an intrinsic. ------------- PR: https://git.openjdk.org/amber/pull/91 From rgiulietti at openjdk.org Fri Mar 10 17:36:40 2023 From: rgiulietti at openjdk.org (Raffaello Giulietti) Date: Fri, 10 Mar 2023 17:36:40 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch [v2] In-Reply-To: References: Message-ID: <76T_lSHKM9bSSNcFIby0Oxm9fVuVSd5Cma0WvHQIAUk=.adb5366c-9b26-4ca0-becb-85984cdb3502@github.com> On Fri, 10 Mar 2023 10:43:54 GMT, Aggelos Biboudis wrote: >> Prototype implementation for primitive types in patterns, instanceof, and switch. >> >> draft JEP: https://openjdk.org/jeps/8288476 >> >> draft spec: https://cr.openjdk.org/~abimpoudis/instanceof/latest/ > > Aggelos Biboudis has updated the pull request incrementally with one additional commit since the last revision: > > Update int_float, long_float, long_double > > Co-authored-by: Angelos Bimpoudis > Co-authored-by: Raffaello Giulietti src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 38: > 36: > 37: private ExactnessMethods() { } > 38: Suggestion: public static boolean isNegativeZero(float n) { return Float.floatToIntRawBits(n) == Integer.MIN_VALUE; } public static boolean isNegativeZero(double n) { return Double.doubleToRawLongBits(n) == Long.MIN_VALUE; } src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 169: > 167: * > 168: * */ > 169: public static boolean float_byte(float n) {return Float.compare(n, (float)(byte)(n)) == 0;} Suggestion: public static boolean float_byte(float n) { return n == (float)(byte)n && !isNegativeZero(n); } src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 177: > 175: * > 176: * */ > 177: public static boolean float_short(float n) {return Float.compare(n, (float)(short)(n)) == 0;} Suggestion: public static boolean float_short(float n) { return n == (float)(short)n && !isNegativeZero(n); } src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 185: > 183: * > 184: * */ > 185: public static boolean float_char(float n) {return Float.compare(n, (float)(char)(n)) == 0;} Suggestion: public static boolean float_char(float n) { return n == (float)(char)n && !isNegativeZero(n); } src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 194: > 192: * */ > 193: public static boolean float_int(float n) { > 194: return Double.compare((double)n, (double)((int)n)) == 0; Suggestion: return n == (float)(int)n && n != 0x1p31f && !isNegativeZero(n); src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 208: > 206: Float.compare(n, Float.NEGATIVE_INFINITY) == 0 || > 207: Float.compare(n, Float.POSITIVE_INFINITY) == 0) return false; > 208: return n == (long)n && n != (float)Long.MAX_VALUE + 1; Suggestion: return n == (float)(long)n && n != 0x1p63f && !isNegativeZero(n); src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 217: > 215: * > 216: * */ > 217: public static boolean double_byte(double n) {return Double.compare(n, (double)(byte)(n)) == 0;} Suggestion: public static boolean double_byte(double n) { return n == (double)(byte)n && !isNegativeZero(n); } src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 225: > 223: * > 224: * */ > 225: public static boolean double_short(double n){return Double.compare(n, (double)(short)(n)) == 0;} Suggestion: public static boolean double_short(double n){ return n == (double)(short)n && !isNegativeZero(n); } src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 233: > 231: * > 232: * */ > 233: public static boolean double_char(double n) {return Double.compare(n, (double)(char)(n)) == 0;} Suggestion: public static boolean double_char(double n) { return n == (double)(char)n && !isNegativeZero(n); } src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 241: > 239: * > 240: * */ > 241: public static boolean double_int(double n) {return Double.compare(n, (double)(int)(n)) == 0;} Suggestion: public static boolean double_int(double n) { return n == (double)(int)n && !isNegativeZero(n); } src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 254: > 252: Double.compare(n, Double.NEGATIVE_INFINITY) == 0 || > 253: Double.compare(n, Double.POSITIVE_INFINITY) == 0) return false; > 254: return n == (long)n && n != (double)Long.MAX_VALUE + 1; Suggestion: return n == (double)(long)n && n != 0x1p63 && !isNegativeZero(n); src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 263: > 261: * > 262: * */ > 263: public static boolean double_float(double n) {return Double.compare(n, (double)(float)(n)) == 0;} Suggestion: public static boolean double_float(double n) { return n == (double)(float)n || n != n; } ------------- PR: https://git.openjdk.org/amber/pull/91 From rgiulietti at openjdk.org Fri Mar 10 17:36:40 2023 From: rgiulietti at openjdk.org (Raffaello Giulietti) Date: Fri, 10 Mar 2023 17:36:40 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch [v2] In-Reply-To: <76T_lSHKM9bSSNcFIby0Oxm9fVuVSd5Cma0WvHQIAUk=.adb5366c-9b26-4ca0-becb-85984cdb3502@github.com> References: <76T_lSHKM9bSSNcFIby0Oxm9fVuVSd5Cma0WvHQIAUk=.adb5366c-9b26-4ca0-becb-85984cdb3502@github.com> Message-ID: On Fri, 10 Mar 2023 17:16:51 GMT, Raffaello Giulietti wrote: >> Aggelos Biboudis has updated the pull request incrementally with one additional commit since the last revision: >> >> Update int_float, long_float, long_double >> >> Co-authored-by: Angelos Bimpoudis >> Co-authored-by: Raffaello Giulietti > > src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 38: > >> 36: >> 37: private ExactnessMethods() { } >> 38: > > Suggestion: > > public static boolean isNegativeZero(float n) { > return Float.floatToIntRawBits(n) == Integer.MIN_VALUE; > } > > public static boolean isNegativeZero(double n) { > return Double.doubleToRawLongBits(n) == Long.MIN_VALUE; > } These will be used below, and could be `private` if so preferred. ------------- PR: https://git.openjdk.org/amber/pull/91 From duke at openjdk.org Fri Mar 10 18:15:39 2023 From: duke at openjdk.org (Michael Hixson) Date: Fri, 10 Mar 2023 18:15:39 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch [v2] In-Reply-To: References: <5RwFlDuN7k_aoTPAwZP8kS0lIum9-sPAyLqj9ivyOlU=.85872b6d-0303-49a8-a02d-7f3697a1c1c2@github.com> Message-ID: <-C_Gcb4gn5NycF4QS4rvvXaafiKARwYIj-hzZINe83o=.f4c87f85-af4f-4a76-bbfd-b1ba12e941a7@github.com> On Fri, 10 Mar 2023 13:56:23 GMT, Raffaello Giulietti wrote: >> src/java.base/share/classes/java/lang/runtime/ExactnessMethods.java line 178: >> >>> 176: (64 - (Long.numberOfLeadingZeros(n) + >>> 177: Long.numberOfTrailingZeros(n))) ; >>> 178: } >> >> Are you interested in optimizing the performance of these methods at this time? >> >> For example, in this method: >> * You can remove the `Long.MIN_VALUE` check. That value "accidentally" works out with the rest of the logic; it has no leading zeros and 63 trailing zeros. This optimization assumes that `Long.MIN_VALUE` isn't common enough to deserve its own fast path (at the expense of every other input), which seems true. >> * If you assume that most inputs to this method are "small" integers that do fit in doubles (which I think is true), you can optimize for them by checking trailing zeros lazily. >> ```java >> n = Math.abs(n); >> int z = Long.numberOfLeadingZeros(n); >> return z > 10 || z + Long.numberOfTrailingZeros(n) > 10; >> ``` >> >> I can share benchmarks if that's useful. (I needed this method years ago, and I wrote benchmarks back then.) >> >> I'm assuming these methods are invoked whenever an expression like `longValue instanceof double` is evaluated, and that these methods aren't intrinsified (yet), so they seem worth optimizing. But I could be mistaken. > > @michaelhixson The more compact variant given above in the comment by @biboudis, compiles to minimal machine code. There's hardly any need for an intrinsic. @rgiulietti In local benchmarks, the compact version of `long_double` is slower than what was in the PR originally, which in turn is slower than the "lazy trailing zeros" version in my previous comment. Did you arrive at that compact form through benchmarking? If so, great - my benchmarks might be bad or I might have conflicting results because I'm on Windows. If not, I'll suggest that faster forms of this method may exist, which may be worth investigating... eventually. ------------- PR: https://git.openjdk.org/amber/pull/91 From abimpoudis at openjdk.org Sun Mar 12 08:43:38 2023 From: abimpoudis at openjdk.org (Aggelos Biboudis) Date: Sun, 12 Mar 2023 08:43:38 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch [v3] In-Reply-To: References: Message-ID: > Prototype implementation for primitive types in patterns, instanceof, and switch. > > draft JEP: https://openjdk.org/jeps/8288476 > > draft spec: https://cr.openjdk.org/~abimpoudis/instanceof/latest/ Aggelos Biboudis has updated the pull request incrementally with one additional commit since the last revision: Improve ExactnessMethods Co-authored-by: Raffaello Giulietti Co-authored-by: Angelos Bimpoudis ------------- Changes: - all: https://git.openjdk.org/amber/pull/91/files - new: https://git.openjdk.org/amber/pull/91/files/80380474..20b205e2 Webrevs: - full: https://webrevs.openjdk.org/?repo=amber&pr=91&range=02 - incr: https://webrevs.openjdk.org/?repo=amber&pr=91&range=01-02 Stats: 189 lines in 2 files changed: 170 ins; 8 del; 11 mod Patch: https://git.openjdk.org/amber/pull/91.diff Fetch: git fetch https://git.openjdk.org/amber pull/91/head:pull/91 PR: https://git.openjdk.org/amber/pull/91 From abimpoudis at openjdk.org Sun Mar 12 09:18:56 2023 From: abimpoudis at openjdk.org (Aggelos Biboudis) Date: Sun, 12 Mar 2023 09:18:56 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch [v3] In-Reply-To: <-C_Gcb4gn5NycF4QS4rvvXaafiKARwYIj-hzZINe83o=.f4c87f85-af4f-4a76-bbfd-b1ba12e941a7@github.com> References: <5RwFlDuN7k_aoTPAwZP8kS0lIum9-sPAyLqj9ivyOlU=.85872b6d-0303-49a8-a02d-7f3697a1c1c2@github.com> <-C_Gcb4gn5NycF4QS4rvvXaafiKARwYIj-hzZINe83o=.f4c87f85-af4f-4a76-bbfd-b1ba12e941a7@github.com> Message-ID: On Fri, 10 Mar 2023 18:12:32 GMT, Michael Hixson wrote: >> @michaelhixson The more compact variant given above in the comment by @biboudis, compiles to minimal machine code. There's hardly any need for an intrinsic. > > @rgiulietti In local benchmarks, the compact version of `long_double` is slower than what was in the PR originally, which in turn is slower than the "lazy trailing zeros" version in my previous comment. > > Did you arrive at that compact form through benchmarking? If so, great - my benchmarks might be bad or I might have conflicting results because I'm on Windows. If not, I'll suggest that faster forms of this method may exist, which may be worth investigating... eventually. I added a benchmarks file (we may or may not keep it in the final PR) that we can discuss during the finalization of the PR on openjdk/jdk/. In any case, feel free to take a look on the test. I followed these [instructions](https://openjdk.org/groups/build/doc/testing.html) for more info. The following are quick measurements on an Apple M1 Max chip/macOS 13.2.1 (22D68) -- quick meaning that if we want to keep them, we should run them with 3 forks for 15wi/15i at least. sh make/devkit/createJMHBundle.sh bash configure --with-boot-jdk="<.....>/prebuilt/jdk-19.jdk/Contents/Home/" --with-jmh=<.....>/jdk/make/devkit/../../build/jmh/jars/ -disable-warnings-as-errors make test TEST="micro:org.openjdk.bench.jdk.preview.patterns.Exactness" MICRO="OPTIONS=-p "pollute=true";VM_OPTIONS=--enable-native-access=ALL-UNNAMED;RESULTS_FORMAT=json" CONF=rel Exactness.test_float_int_based_on_compare avgt 5 22001.714 ? 21.124 ms/op Exactness.test_float_int_based_on_filtering avgt 5 21459.505 ? 159.797 ms/op Exactness.test_int_float_based_on_filtering avgt 5 1464.611 ? 5.375 ms/op Exactness.test_int_float_based_on_leading_trailing avgt 5 2741.839 ? 10.300 ms/op Exactness.test_long_double_based_on_filtering avgt 5 1379.058 ? 4.329 ms/op Exactness.test_long_double_based_on_leading_trailing avgt 5 2439.624 ? 4.366 ms/op Exactness.test_long_float_based_on_filtering avgt 5 1491.983 ? 8.019 ms/op Exactness.test_long_float_based_on_leading_trailing avgt 5 2736.461 ? 9.508 ms/op Interesting that both float_int are the same and I observe that the filtering ones (or compact) are better than all the analytical (leading_trailing). ------------- PR: https://git.openjdk.org/amber/pull/91 From duke at openjdk.org Sun Mar 12 18:09:46 2023 From: duke at openjdk.org (Michael Hixson) Date: Sun, 12 Mar 2023 18:09:46 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch [v3] In-Reply-To: References: <5RwFlDuN7k_aoTPAwZP8kS0lIum9-sPAyLqj9ivyOlU=.85872b6d-0303-49a8-a02d-7f3697a1c1c2@github.com> <-C_Gcb4gn5NycF4QS4rvvXaafiKARwYIj-hzZINe83o=.f4c87f85-af4f-4a76-bbfd-b1ba12e941a7@github.com> Message-ID: <6ldNXSofCNeWs_Avw7GRDeKm6ijuSpQwgbMZW4zYIFc=.10e06957-5c37-47f7-b297-2660e9be2bf2@github.com> On Sun, 12 Mar 2023 09:16:17 GMT, Aggelos Biboudis wrote: >> @rgiulietti In local benchmarks, the compact version of `long_double` is slower than what was in the PR originally, which in turn is slower than the "lazy trailing zeros" version in my previous comment. >> >> Did you arrive at that compact form through benchmarking? If so, great - my benchmarks might be bad or I might have conflicting results because I'm on Windows. If not, I'll suggest that faster forms of this method may exist, which may be worth investigating... eventually. > > I added a benchmarks file (we may or may not keep it in the final PR) that we can discuss during the finalization of the PR on openjdk/jdk/. In any case, feel free to take a look on the test. > > I followed these [instructions](https://openjdk.org/groups/build/doc/testing.html) for more info. > > The following are quick measurements on an Apple M1 Max chip/macOS 13.2.1 (22D68) -- quick meaning that if we want to keep them, we should run them with 3 forks for 15wi/15i at least. > > > sh make/devkit/createJMHBundle.sh > > bash configure --with-boot-jdk="<.....>/prebuilt/jdk-19.jdk/Contents/Home/" --with-jmh=<.....>/jdk/make/devkit/../../build/jmh/jars/ -disable-warnings-as-errors > > make test TEST="micro:org.openjdk.bench.jdk.preview.patterns.Exactness" MICRO="OPTIONS=-p "pollute=true";VM_OPTIONS=--enable-native-access=ALL-UNNAMED;RESULTS_FORMAT=json" CONF=rel > > Exactness.test_float_int_based_on_compare avgt 5 22001.714 ? 21.124 ms/op > Exactness.test_float_int_based_on_filtering avgt 5 21459.505 ? 159.797 ms/op > Exactness.test_int_float_based_on_filtering avgt 5 1464.611 ? 5.375 ms/op > Exactness.test_int_float_based_on_leading_trailing avgt 5 2741.839 ? 10.300 ms/op > Exactness.test_long_double_based_on_filtering avgt 5 1379.058 ? 4.329 ms/op > Exactness.test_long_double_based_on_leading_trailing avgt 5 2439.624 ? 4.366 ms/op > Exactness.test_long_float_based_on_filtering avgt 5 1491.983 ? 8.019 ms/op > Exactness.test_long_float_based_on_leading_trailing avgt 5 2736.461 ? 9.508 ms/op > > > Interesting that both float_int are the same and I observe that the filtering ones (or compact) are better than all the analytical (leading_trailing). @biboudis It's interesting that your `long_double` results conflict with mine. My results on an i7-7700K + Windows: Benchmark Mode Cnt Score Error Units Exactness.test_long_double_based_on_filtering avgt 5 4579.436 ? 66.288 ms/op Exactness.test_long_double_based_on_leading_trailing avgt 5 3322.384 ? 110.679 ms/op My results on an i9-7960X + Windows: Benchmark Mode Cnt Score Error Units Exactness.test_long_double_based_on_filtering avgt 5 5403.940 ? 186.774 ms/op Exactness.test_long_double_based_on_leading_trailing avgt 5 3828.349 ? 81.673 ms/op I've never been able to build the JDK, so I copied your test code into a standalone project with Java 19 and JMH 1.36. So there's that, the processors, and the operating systems as possible sources of the difference. I'm not sure how to tell whose results are more "correct", if there is such a thing. I suppose that if the performance is a wash, the filtering approach is more attractive: it's easier to understand and it's stylistically similar to the rest of the exactness methods. Thanks for trying out the benchmarks anyway. ------------- PR: https://git.openjdk.org/amber/pull/91 From rgiulietti at openjdk.org Mon Mar 13 13:35:39 2023 From: rgiulietti at openjdk.org (Raffaello Giulietti) Date: Mon, 13 Mar 2023 13:35:39 GMT Subject: [patterns-instanceof-primitive] RFR: 8303374: Compiler Implementation for Primitive types in patterns, instanceof, and switch [v3] In-Reply-To: <6ldNXSofCNeWs_Avw7GRDeKm6ijuSpQwgbMZW4zYIFc=.10e06957-5c37-47f7-b297-2660e9be2bf2@github.com> References: <5RwFlDuN7k_aoTPAwZP8kS0lIum9-sPAyLqj9ivyOlU=.85872b6d-0303-49a8-a02d-7f3697a1c1c2@github.com> <-C_Gcb4gn5NycF4QS4rvvXaafiKARwYIj-hzZINe83o=.f4c87f85-af4f-4a76-bbfd-b1ba12e941a7@github.com> <6ldNXSofCNeWs_Avw7GRDeKm6ijuSpQwgbMZW4zYIFc=.10e06957-5c37-47f7-b297-2660e9be2bf2@github.com> Message-ID: On Sun, 12 Mar 2023 18:07:00 GMT, Michael Hixson wrote: >> I added a benchmarks file (we may or may not keep it in the final PR) that we can discuss during the finalization of the PR on openjdk/jdk/. In any case, feel free to take a look on the test. >> >> I followed these [instructions](https://openjdk.org/groups/build/doc/testing.html) for more info. >> >> The following are quick measurements on an Apple M1 Max chip/macOS 13.2.1 (22D68) -- quick meaning that if we want to keep them, we should run them with 3 forks for 15wi/15i at least. >> >> >> sh make/devkit/createJMHBundle.sh >> >> bash configure --with-boot-jdk="<.....>/prebuilt/jdk-19.jdk/Contents/Home/" --with-jmh=<.....>/jdk/make/devkit/../../build/jmh/jars/ -disable-warnings-as-errors >> >> make test TEST="micro:org.openjdk.bench.jdk.preview.patterns.Exactness" MICRO="OPTIONS=-p "pollute=true";VM_OPTIONS=--enable-native-access=ALL-UNNAMED;RESULTS_FORMAT=json" CONF=rel >> >> Exactness.test_float_int_based_on_compare avgt 5 22001.714 ? 21.124 ms/op >> Exactness.test_float_int_based_on_filtering avgt 5 21459.505 ? 159.797 ms/op >> Exactness.test_int_float_based_on_filtering avgt 5 1464.611 ? 5.375 ms/op >> Exactness.test_int_float_based_on_leading_trailing avgt 5 2741.839 ? 10.300 ms/op >> Exactness.test_long_double_based_on_filtering avgt 5 1379.058 ? 4.329 ms/op >> Exactness.test_long_double_based_on_leading_trailing avgt 5 2439.624 ? 4.366 ms/op >> Exactness.test_long_float_based_on_filtering avgt 5 1491.983 ? 8.019 ms/op >> Exactness.test_long_float_based_on_leading_trailing avgt 5 2736.461 ? 9.508 ms/op >> >> >> Interesting that both float_int are the same and I observe that the filtering ones (or compact) are better than all the analytical (leading_trailing). > > @biboudis It's interesting that your `long_double` results conflict with mine. > > My results on an i7-7700K + Windows: > > Benchmark Mode Cnt Score Error Units > Exactness.test_long_double_based_on_filtering avgt 5 4579.436 ? 66.288 ms/op > Exactness.test_long_double_based_on_leading_trailing avgt 5 3322.384 ? 110.679 ms/op > > > My results on an i9-7960X + Windows: > > Benchmark Mode Cnt Score Error Units > Exactness.test_long_double_based_on_filtering avgt 5 5403.940 ? 186.774 ms/op > Exactness.test_long_double_based_on_leading_trailing avgt 5 3828.349 ? 81.673 ms/op > > > I've never been able to build the JDK, so I copied your test code into a standalone project with Java 19 and JMH 1.36. So there's that, the processors, and the operating systems as possible sources of the difference. > > I'm not sure how to tell whose results are more "correct", if there is such a thing. I suppose that if the performance is a wash, the filtering approach is more attractive: it's easier to understand and it's stylistically similar to the rest of the exactness methods. > > Thanks for trying out the benchmarks anyway. > > --- > > **Edit:** I can't tell if M1 supports the `lzcnt` and `tzcnt` instructions. For me, the generated assembly of the benchmark uses those for `Long.numberOf{Leading,Trailing}Zeros`. If it's falling back to something less efficient on M1, that could explain things. Seems like there are fast integer<->floating-point conversion instructions on Aarch64 with semantics very similar to Java's, but that on x86-64 the conversions are rather convoluted. However, if the check is then followed by the cast, as in a successful `instanceof`, for example, then maybe the "filtering" variant works well even of x86-64, because the result might already be available in a register from the preceding check. Hard to tell from the results above, though. ------------- PR: https://git.openjdk.org/amber/pull/91 From mark.reinhold at oracle.com Wed Mar 15 17:58:43 2023 From: mark.reinhold at oracle.com (Mark Reinhold) Date: Wed, 15 Mar 2023 17:58:43 +0000 Subject: New candidate JEP: 440: Record Patterns Message-ID: <20230315175842.CAE485CF16B@eggemoggin.niobe.net> https://openjdk.org/jeps/440 Summary: Enhance the Java programming language with record patterns to deconstruct record values. Record patterns and type patterns can be nested to enable a powerful, declarative, and composable form of data navigation and processing. - Mark From mark.reinhold at oracle.com Wed Mar 15 17:58:47 2023 From: mark.reinhold at oracle.com (Mark Reinhold) Date: Wed, 15 Mar 2023 17:58:47 +0000 Subject: New candidate JEP: 441: Pattern Matching for switch Message-ID: <20230315175846.E1E535CF16D@eggemoggin.niobe.net> https://openjdk.org/jeps/441 Summary: Enhance the Java programming language with pattern matching for switch expressions and statements. Extending pattern matching to switch allows an expression to be tested against a number of patterns, each with a specific action, so that complex data-oriented queries can be expressed concisely and safely. - Mark From davidalayachew at gmail.com Sat Mar 18 03:59:01 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Fri, 17 Mar 2023 23:59:01 -0400 Subject: New candidate JEP: 441: Pattern Matching for switch Message-ID: Hello Amber Dev Team, I am very excited for this feature. I think the new levels of exhaustiveness that this feature enables will remove a common source of bugs for me when I write code. Looking at the link, I am very happy to see that switch expressions will now play nicer with enums under a sealed type. This was a pain point for code I was writing a few months ago [1], so having this will streamline my development by a lot. I also very much like to see that parenthesized patterns are being removed, to be handled later. In general, I like the idea of simply deferring the uncommon edge cases to be dealt with in the future. Curious to see how it will be dealt with in the future. That said, could we add a short description of what parenthesized patterns are? Perhaps I missed a discussion on it, but I didn't even know that they existed until I saw this JEP back when it was a JEP Draft. And if not a description, maybe a link to some official Oracle documentation? This link [2] is how I figured out what parenthesized patterns are. Thank you all for your time and effort! David Alayachew [1] = https://mail.openjdk.org/pipermail/amber-dev/2022-October/007528.html [2] = https://docs.oracle.com/en/java/javase/19/language/pattern-matching.html#GUID-3EEBECEA-D874-4B20-B46B-07ED6172881D -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Sat Mar 18 06:05:36 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Sat, 18 Mar 2023 02:05:36 -0400 Subject: New candidate JEP: 440: Record Patterns Message-ID: Hello Amber Dev Team, I'm very happy to see this feature is almost ready to release for General Availability. Java 21 can't arrive soon enough. Looking at the link, I have a few comments. I'll make each comment a separate thread to facilitate discussion. I see that record patterns in for each loop are being dropped, which makes sense. I definitely agree with this decision. I am curious though - what are your thoughts on having multiple, super granular JEP's that only do one thing, and then work on several of them at once? Is the current form more preferred? I ask this because there have been a couple of things being dropped from JEP's 440 and 441. And to be clear, this is a very good thing - deferring the edge cases for later when you might have a better answer sounds like the same conservative approach that allowed the language to avoid potholes by being a last mover. I think that this is a wise mindset. I am just curious if breaking JEP's up into a bunch of super granular mini-JEP's might actually facilitate this sort of last mover mindset. There are some things that are obviously right and can be comfortably pushed to GA after a minimal number of iterations (if any), while others may require some reworking and several iterations. On top of that, there are some issues that better come to light when you have one part of a major feature in GA, but another part still being iterated on in preview. Being able to send out the pieces like that may even result in better solutions that would be harder to discover when everything is in preview all at once. Furthermore, I think it would allow the developers looking in to be able to better see what is and isn't enabled by certain JEP's. While I understand that we don't need to cater to those who won't take the effort to read, the simple reality is that there are plenty of devs who see the giant wall of text that is the JEP's, then just skip to the code examples and start commenting. Taking on the extra overhead may enable the community to have better informed commentary, and allow us to discover pain points that might get hidden in the details with the normal sized JEP's we are all used to. Hidden is definitely the wrong word to use here, but I want to communicate the perspective of those who act like how I mentioned immediately above That said, I admit - this likely involves a lot more overhead for the team. I don't have any sort of picture of how much, but I readily concede that this can easily be a dealbreaker. Thank you for your time and help! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Sat Mar 18 06:07:45 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Sat, 18 Mar 2023 02:07:45 -0400 Subject: New candidate JEP: 440: Record Patterns Message-ID: Hello Amber Dev Team, In the Record patterns section of the link (little more than halfway down). It says the following quote. --- The null value does not match any record pattern. --- I understand that this is relatively clear and simple enough to understand, but in the name of explicitness and beginner-friendlyness, could we add something like this? I understand that it is redundant, but this extra effort will help uproot a lot of the misinformation that people will inevitably come up with. --- The null value does not match any record pattern. For example, the following code will print "No match". record Point(Integer x, Integer y) {} enum Color { RED, GREEN, BLUE; } record ColoredPoint(Point p, Color c) {} ColoredPoint cp = null; if (cp instanceof ColoredPoint(Point(Integer x, Integer y), Color c)) { System.out.println("Match 1! x = " + x + " and y = " + y + " and c = " + c); } else if (cp instanceof ColoredPoint(Point p, Color c)) { System.out.println("Match 2! p = " + p + " and c = " + c); } else { System.out.println("No match"); } However, if we change cp to be the following cp = new ColoredPoint(null, null); then the output would be "Match 2! p = null and c = null". And if we change cp to be the following cp = new ColoredPoint(new Point(null, null), null); then the output would be "Match 1! x = null and y = null and c = null" --- I understand that it feels excessive, but this level of clarity and explicitness makes reasoning about the rest of the JEP a lot easier. null is always at the back of my mind, so having explicit examples of how it plays with the new features clears up the ambiguity and makes understanding the new features that much easier. Thank you for your time! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Sat Mar 18 06:22:19 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Sat, 18 Mar 2023 02:22:19 -0400 Subject: New candidate JEP: 440: Record Patterns Message-ID: Hello Amber Dev Team, Near the bottom of Record patterns (right above Record patterns and exhaustive switch), there is the following quote. --- For compatibility, type patterns do not support the implicit inference of type arguments; e.g., the type pattern List l is always treated as a raw type pattern. --- This makes a lot of sense, and I certainly understand why this is the case. But that does mean that there is an inconsistency. If I start off with a record, and then later realize that it should instead be a class with deconstructors, this may be a difficult to find bug in my code. At least, that's what it seems like without any testing. Is this inconsistency something that may be smoothed out further down the line? More specifically, should records be given this privilege if classes can't receive it too? Perhaps I am missing something, and thus, I'm just misinterpreting? Thank you for your time and help! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From holo3146 at gmail.com Sat Mar 18 10:33:29 2023 From: holo3146 at gmail.com (Holo The Sage Wolf) Date: Sat, 18 Mar 2023 12:33:29 +0200 Subject: New candidate JEP: 441: Pattern Matching for switch In-Reply-To: References: Message-ID: JEP 433, 427, 420, 406 all have a word about parenthesized patterns. Maybe an example like in your [2] would have been more clear, but the existence and grammar of parenthesized patterns existent from the first preview On Sat, Mar 18, 2023 at 5:59?AM David Alayachew wrote: > Hello Amber Dev Team, > > I am very excited for this feature. I think the new levels of > exhaustiveness that this feature enables will remove a common source of > bugs for me when I write code. > > Looking at the link, I am very happy to see that switch expressions will > now play nicer with enums under a sealed type. This was a pain point for > code I was writing a few months ago [1], so having this will streamline my > development by a lot. > > I also very much like to see that parenthesized patterns are being > removed, to be handled later. In general, I like the idea of simply > deferring the uncommon edge cases to be dealt with in the future. Curious > to see how it will be dealt with in the future. > > That said, could we add a short description of what parenthesized patterns > are? Perhaps I missed a discussion on it, but I didn't even know that they > existed until I saw this JEP back when it was a JEP Draft. And if not a > description, maybe a link to some official Oracle documentation? This link > [2] is how I figured out what parenthesized patterns are. > > Thank you all for your time and effort! > David Alayachew > > [1] = > https://mail.openjdk.org/pipermail/amber-dev/2022-October/007528.html > > [2] = > https://docs.oracle.com/en/java/javase/19/language/pattern-matching.html#GUID-3EEBECEA-D874-4B20-B46B-07ED6172881D > > -- Holo The Wise Wolf Of Yoitsu -------------- next part -------------- An HTML attachment was scrubbed... URL: From holo3146 at gmail.com Sat Mar 18 10:54:22 2023 From: holo3146 at gmail.com (Holo The Sage Wolf) Date: Sat, 18 Mar 2023 12:54:22 +0200 Subject: New candidate JEP: 440: Record Patterns In-Reply-To: References: Message-ID: Hello David, There are several problems I can see from using mini-JEP for a new language feature, the biggest one of them is that counter-intuitively I believe it will slow down adaptation and slow down user-feedback loop. If, for example, we separate in JEP 440 the "base" instanceof-pattern, the nested-pattern and the generic-pattern, a lot of people will either feel unsatisfied from the JEPs and will just wait (and in this case, by the time they will give feedback, the "base"-pattern will already be late in the releasing cycle) or they will use hacks to implement the missing language features (and in this case, their feedback does not reflect the "meat" of the JEP),I am definitely guilty for doing this. I think that mini-JEPs can work in enhancements to existing features (I am aware of the irony), e.g. for-pattern that was dropped, generalized matcher (which is being discussed in the expert-mailing group), parenthesized patterns as you discussed in a different thread. Even with the very big JEPs of Record patterns and Pattern Matching for switch I definitely feel like people accidently give feedback to the wrong JEP between the 2 a lot. On Sat, Mar 18, 2023 at 8:06?AM David Alayachew wrote: > Hello Amber Dev Team, > > I'm very happy to see this feature is almost ready to release for General > Availability. Java 21 can't arrive soon enough. > > Looking at the link, I have a few comments. I'll make each comment a > separate thread to facilitate discussion. > > I see that record patterns in for each loop are being dropped, which makes > sense. I definitely agree with this decision. > > I am curious though - what are your thoughts on having multiple, super > granular JEP's that only do one thing, and then work on several of them at > once? Is the current form more preferred? > > I ask this because there have been a couple of things being dropped from > JEP's 440 and 441. And to be clear, this is a very good thing - deferring > the edge cases for later when you might have a better answer sounds like > the same conservative approach that allowed the language to avoid potholes > by being a last mover. I think that this is a wise mindset. > > I am just curious if breaking JEP's up into a bunch of super granular > mini-JEP's might actually facilitate this sort of last mover mindset. There > are some things that are obviously right and can be comfortably pushed to > GA after a minimal number of iterations (if any), while others may require > some reworking and several iterations. > > On top of that, there are some issues that better come to light when you > have one part of a major feature in GA, but another part still being > iterated on in preview. Being able to send out the pieces like that may > even result in better solutions that would be harder to discover when > everything is in preview all at once. > > Furthermore, I think it would allow the developers looking in to be able > to better see what is and isn't enabled by certain JEP's. While I > understand that we don't need to cater to those who won't take the effort > to read, the simple reality is that there are plenty of devs who see the > giant wall of text that is the JEP's, then just skip to the code examples > and start commenting. Taking on the extra overhead may enable the community > to have better informed commentary, and allow us to discover pain points > that might get hidden in the details with the normal sized JEP's we are all > used to. Hidden is definitely the wrong word to use here, but I want to > communicate the perspective of those who act like how I mentioned > immediately above > > That said, I admit - this likely involves a lot more overhead for the > team. I don't have any sort of picture of how much, but I readily concede > that this can easily be a dealbreaker. > > Thank you for your time and help! > David Alayachew > > -- Holo The Wise Wolf Of Yoitsu -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Sat Mar 18 14:57:38 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 18 Mar 2023 10:57:38 -0400 Subject: New candidate JEP: 440: Record Patterns In-Reply-To: References: Message-ID: <219b2e75-35c6-5381-a5ff-3d9dff138d93@oracle.com> > I am curious though - what are your thoughts on having multiple, super > granular JEP's that only do one thing, and then work on several of > them at once? Is the current form more preferred? On a historical scale, these JEPs could be described as "super granular"!? Compare the N pattern matching JEPs so far (and more coming) to a feature like Lambda, which was one big JSR (including the streams library).? Which is to say, there's always room to make the glass bigger or smaller. The forces that we are constantly trying to balance are: ?- Doing too much at once risks taking a long time before we deliver anything; ?- Doing too little in one JEP means that the feature doesn't cleanly "stand alone"; ?- Each JEP has some overhead, and doing too many is impractical. In particular, having multiple JEPs for features that touch the same parts of the spec can be much more expensive, especially if they are not on the same track for when they will finalize. The right balance will vary depending on the feature, the degree of coupling between sub-features, etc.? I'm sure that we'll continue to adjust, and in some cases, over-rotate.? But I don't think a "let's break each feature into ten JEPs, so we can late-bind to which sub-features we take" is a good idea in general. From davidalayachew at gmail.com Sat Mar 18 22:14:41 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Sat, 18 Mar 2023 18:14:41 -0400 Subject: New candidate JEP: 441: Pattern Matching for switch In-Reply-To: References: Message-ID: Hello Holo, Thank you for your response! > JEP 433, 427, 420, 406 all have a word > about parenthesized patterns. Maybe an > example like in your [2] would have been > more clear, but the existence and > grammar of parenthesized patterns > existent from the first preview. Looking back at them, you are definitely right. They all do mention it like you said. But reading each JEP's section on it, I think I see where I was coming from when I originally made my point. I saw that you can put parentheses around the pattern, but they pretty much just said "improves readability". The links I mentioned said that, then showed you what the parentheses were protecting you from. Specifically, parentheses allow you to use lambdas and switch patterns together without messing up the order of operations. And like you said, the code example makes it entirely unambiguous. Could we add a code example for this JEP? If not that, then maybe just link to the equivalent of what I linked, but for the respective version of Java? Thank you for your help! David Alayachew On Sat, Mar 18, 2023, 6:33 AM Holo The Sage Wolf wrote: > JEP 433, 427, 420, 406 all have a word about parenthesized patterns. > Maybe an example like in your [2] would have been more clear, but the > existence and grammar of parenthesized patterns existent from the first > preview > > On Sat, Mar 18, 2023 at 5:59?AM David Alayachew > wrote: > >> Hello Amber Dev Team, >> >> I am very excited for this feature. I think the new levels of >> exhaustiveness that this feature enables will remove a common source of >> bugs for me when I write code. >> >> Looking at the link, I am very happy to see that switch expressions will >> now play nicer with enums under a sealed type. This was a pain point for >> code I was writing a few months ago [1], so having this will streamline my >> development by a lot. >> >> I also very much like to see that parenthesized patterns are being >> removed, to be handled later. In general, I like the idea of simply >> deferring the uncommon edge cases to be dealt with in the future. Curious >> to see how it will be dealt with in the future. >> >> That said, could we add a short description of what parenthesized >> patterns are? Perhaps I missed a discussion on it, but I didn't even know >> that they existed until I saw this JEP back when it was a JEP Draft. And if >> not a description, maybe a link to some official Oracle documentation? This >> link [2] is how I figured out what parenthesized patterns are. >> >> Thank you all for your time and effort! >> David Alayachew >> >> [1] = >> https://mail.openjdk.org/pipermail/amber-dev/2022-October/007528.html >> >> [2] = >> https://docs.oracle.com/en/java/javase/19/language/pattern-matching.html#GUID-3EEBECEA-D874-4B20-B46B-07ED6172881D >> >> > > -- > Holo The Wise Wolf Of Yoitsu > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Sat Mar 18 22:17:27 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 18 Mar 2023 18:17:27 -0400 Subject: New candidate JEP: 441: Pattern Matching for switch In-Reply-To: References: Message-ID: When a JEP for a preview feature goes final, its job is to describe the feature being shipped, not the historical arc.? Putting something about a feature we didn't ultimately add would merely be confusing. On 3/18/2023 6:14 PM, David Alayachew wrote: > > Could we add a code example for this JEP? If not that, then maybe just > link to the equivalent of what I linked, but for the respective > version of Java? > -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Sat Mar 18 22:34:49 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Sat, 18 Mar 2023 18:34:49 -0400 Subject: New candidate JEP: 440: Record Patterns In-Reply-To: References: Message-ID: Hello Holo, Thank you for your response! > There are several problems I can see > from using mini-JEP for a new language > feature, the biggest one of them is that > counter-intuitively I believe it will slow > down adaptation and slow down > user-feedback loop. > > If, for example, we separate in JEP 440 > the "base" instanceof-pattern, the > nested-pattern and the generic-pattern, a > lot of people will either feel unsatisfied > from the JEPs and will just wait (and in > this case, by the time they will give > feedback, the "base"-pattern will already > be late in the releasing cycle) or they will > use hacks to implement the missing > language features (and in this case, their > feedback does not reflect the "meat" of > the JEP), I am definitely guilty for doing > this. Lot's of good points here, I see what you are saying. I personally am prone to doing what I described above - taking 1 or 2 features from a JEP, and then try to play with them as much as I can before moving to the next part of the same JEP. But doesn't change the fact that my idea likely would hurt adaptation in general. Makes sense. > I think that mini-JEPs can work in > enhancements to existing features (I am > aware of the irony), e.g. for-pattern that > was dropped, generalized matcher > (which is being discussed in the > expert-mailing group), parenthesized > patterns as you discussed in a different > thread. I understand. And at this point, I think I agree. Sounds like that is more or less what they are doing now then. > Even with the very big JEPs of Record > patterns and Pattern Matching for switch > I definitely feel like people accidently give > feedback to the wrong JEP between the 2 > a lot. And I think this highlights another potential problem with splitting in general - you have a lot of moving parts that slightly resemble each other, so it can be easy to mix them up. Thank you for the insight! David Alayachew > -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Sat Mar 18 22:55:36 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Sat, 18 Mar 2023 18:55:36 -0400 Subject: New candidate JEP: 440: Record Patterns In-Reply-To: <219b2e75-35c6-5381-a5ff-3d9dff138d93@oracle.com> References: <219b2e75-35c6-5381-a5ff-3d9dff138d93@oracle.com> Message-ID: Hello Brian, Thank you for your response! > On a historical scale, these JEPs could > be described as "super granular"! > Compare the N pattern matching JEPs so > far (and more coming) to a feature like > Lambda, which was one big JSR > (including the streams library). Which is > to say, there's always room to make the > glass bigger or smaller. I see what you mean. Even now, I'm still learning all the different ways that Streams and Lambdas and type inference play with each other. In comparison, these JEP's are fairly standalone and easy enough to reason about. > The forces that we are constantly trying > to balance are: > > - Doing too much at once risks taking a > long time before we deliver anything; > > - Doing too little in one JEP means that > the feature doesn't cleanly "stand alone"; > > - Each JEP has some overhead, and > doing too many is impractical. In > particular, having multiple JEPs for > features that touch the same parts > of the spec can be much more > expensive, especially if they are not on > the same track for when they will > finalize. Thanks for explaining the thought process behind this. And I am sure that my idea unbalances the harmony of what you mentioned above. I was more approaching from the direction that maybe having things unbalanced a bit might be worth it because of the improved feedback. But reading yours and Holo's replies have shown that my idea likely wouldn't bring the improved feedback I was looking for. > The right balance will vary depending on > the feature, the degree of coupling > between sub-features, etc. I'm sure that > we'll continue to adjust, and in some > cases, over-rotate. But I don't think a "let's > break each feature into ten JEPs, so we > can late-bind to which sub-features we > take" is a good idea in general. Reading this (and the other replies), it sounds like the way you all are currently doing it is the best way to approach. Thank you for helping me understand! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Sat Mar 18 23:05:34 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Sat, 18 Mar 2023 19:05:34 -0400 Subject: New candidate JEP: 441: Pattern Matching for switch In-Reply-To: References: Message-ID: Hello Brian, > When a JEP for a preview feature goes > final, its job is to describe the feature > being shipped, not the historical arc. > Putting something about a feature we > didn't ultimately add would merely be > confusing. Fair enough. My comment would likely have been relevant back when parenthesized patterns were on the table. In that case, I understand and agree. Thank you for your time! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From mark.reinhold at oracle.com Tue Mar 21 19:17:30 2023 From: mark.reinhold at oracle.com (Mark Reinhold) Date: Tue, 21 Mar 2023 19:17:30 +0000 Subject: New candidate JEP: 443: Unnamed Patterns and Variables (Preview) Message-ID: <20230321191729.15A7C5CFB15@eggemoggin.niobe.net> https://openjdk.org/jeps/443 Summary: Enhance the Java language with unnamed patterns, which match a record component without stating the component's name or type, and unnamed variables, which can be initialized but not used. Both are denoted by an underscore character, _. This is a preview language feature. - Mark From holo3146 at gmail.com Tue Mar 21 21:39:50 2023 From: holo3146 at gmail.com (Holo The Sage Wolf) Date: Tue, 21 Mar 2023 23:39:50 +0200 Subject: New candidate JEP: 443: Unnamed Patterns and Variables (Preview) In-Reply-To: <20230321191729.15A7C5CFB15@eggemoggin.niobe.net> References: <20230321191729.15A7C5CFB15@eggemoggin.niobe.net> Message-ID: Is there a reason not to allow implicit Unnamed Variables in try-with-resources and Catch-block? To allow: try (Scope.open()) { ... } --- try { ... } catch (Exception) { ... } to be implicitly try (var _ = Scope.open()) { ... } --- try { ... } catch (Exception _) { ... } Unlike a normal expression, and expression on the RHS of a try-with-resources variable has an explicit context, there is no fear of someone forgetting the result of the expression by mistake. In a Catch block the exception is uniquely accessible through the variable declared in the declaration of the block, so there is no fear that someone will get confused by this implicitness. This implicitness will make stuff like logging context, which is far from a niche use case, much more elegant. On Tue, Mar 21, 2023 at 9:17?PM Mark Reinhold wrote: > https://openjdk.org/jeps/443 > > Summary: Enhance the Java language with unnamed patterns, which match > a record component without stating the component's name or type, > and unnamed variables, which can be initialized but not used. Both > are denoted by an underscore character, _. This is a preview language > feature. > > - Mark -- Holo The Wise Wolf Of Yoitsu -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Mar 22 12:13:35 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 22 Mar 2023 08:13:35 -0400 Subject: New candidate JEP: 443: Unnamed Patterns and Variables (Preview) In-Reply-To: References: <20230321191729.15A7C5CFB15@eggemoggin.niobe.net> Message-ID: <3399111d-61fd-efb1-8f59-e3e246e588fc@oracle.com> On 3/21/2023 5:39 PM, Holo The Sage Wolf wrote: > > Is there a reason not to allow implicit Unnamed Variables in > try-with-resources and Catch-block? > The JEP already describes these cases: Unnamed variables The following kinds of declarations can introduce either a named variable (denoted by an identifier) or an unnamed variable (denoted by an underscore): * A local variable declaration statement in a block (JLS 14.4.2), * A resource specification of a|try|-with-resources statement (JLS 14.20.3), * The header of a basic|for|statement (JLS 14.14.1), * The header of an enhanced|for|loop (JLS 14.14.2), * An exception parameter of a|catch|block (JLS 14.20), and * A formal parameter of a lambda expression (JLS 15.27.1). Try with resources, and catch blocks, are listed as places where you can use unnamed variables.? Am I undersatnding your question properly? > To allow: > > |try (Scope.open()) { ... } --- try { ... } catch (Exception) { ... } | > > to be implicitly > > |try (var _ = Scope.open()) { ... } --- try { ... } catch (Exception > _) { ... } | > > Unlike a normal expression, and expression on the RHS of a > try-with-resources variable has an explicit context, there is no fear > of someone forgetting the result of the expression by mistake. > > In a Catch block the exception is uniquely accessible through the > variable declared in the declaration of the block, so there is no fear > that someone will get confused by this implicitness. > > This implicitness will make stuff like logging context, which is far > from a niche use case, much more elegant. > > > On Tue, Mar 21, 2023 at 9:17?PM Mark Reinhold > wrote: > > https://openjdk.org/jeps/443 > > ? Summary: Enhance the Java language with unnamed patterns, which > match > ? a record component without stating the component's name or type, > ? and unnamed variables, which can be initialized but not used. Both > ? are denoted by an underscore character, _. This is a preview > language > ? feature. > > - Mark > > > > -- > Holo The Wise Wolf Of Yoitsu -------------- next part -------------- An HTML attachment was scrubbed... URL: From redio.development at gmail.com Wed Mar 22 12:37:40 2023 From: redio.development at gmail.com (Red IO) Date: Wed, 22 Mar 2023 13:37:40 +0100 Subject: New candidate JEP: 443: Unnamed Patterns and Variables (Preview) In-Reply-To: <3399111d-61fd-efb1-8f59-e3e246e588fc@oracle.com> References: <20230321191729.15A7C5CFB15@eggemoggin.niobe.net> <3399111d-61fd-efb1-8f59-e3e246e588fc@oracle.com> Message-ID: I think he suggests to get rid of the variable entirely like in his example: try { } catch (Exception) //<- nothing here { } And try (new AutoclosableImpl()) {//<- no assignment to a variable } Great regards RedIODev On Wed, Mar 22, 2023, 13:19 Brian Goetz wrote: > > > On 3/21/2023 5:39 PM, Holo The Sage Wolf wrote: > > Is there a reason not to allow implicit Unnamed Variables in > try-with-resources and Catch-block? > > > The JEP already describes these cases: > > Unnamed variables > > The following kinds of declarations can introduce either a named variable > (denoted by an identifier) or an unnamed variable (denoted by an > underscore): > > - A local variable declaration statement in a block (JLS 14.4.2), > - A resource specification of a try-with-resources statement (JLS > 14.20.3), > - The header of a basic for statement (JLS 14.14.1), > - The header of an enhanced for loop (JLS 14.14.2), > - An exception parameter of a catch block (JLS 14.20), and > - A formal parameter of a lambda expression (JLS 15.27.1). > > > Try with resources, and catch blocks, are listed as places where you can > use unnamed variables. Am I undersatnding your question properly? > > > > To allow: > > try (Scope.open()) { > ... > } > --- > try { ... } > catch (Exception) { ... } > > to be implicitly > > try (var _ = Scope.open()) { > ... > } > --- > try { ... } > catch (Exception _) { ... } > > Unlike a normal expression, and expression on the RHS of a > try-with-resources variable has an explicit context, there is no fear of > someone forgetting the result of the expression by mistake. > > In a Catch block the exception is uniquely accessible through the variable > declared in the declaration of the block, so there is no fear that someone > will get confused by this implicitness. > > This implicitness will make stuff like logging context, which is far from > a niche use case, much more elegant. > > On Tue, Mar 21, 2023 at 9:17?PM Mark Reinhold > wrote: > >> https://openjdk.org/jeps/443 >> >> Summary: Enhance the Java language with unnamed patterns, which match >> a record component without stating the component's name or type, >> and unnamed variables, which can be initialized but not used. Both >> are denoted by an underscore character, _. This is a preview language >> feature. >> >> - Mark > > > > -- > Holo The Wise Wolf Of Yoitsu > > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From holo3146 at gmail.com Wed Mar 22 13:31:21 2023 From: holo3146 at gmail.com (Holo The Sage Wolf) Date: Wed, 22 Mar 2023 15:31:21 +0200 Subject: New candidate JEP: 443: Unnamed Patterns and Variables (Preview) In-Reply-To: References: <20230321191729.15A7C5CFB15@eggemoggin.niobe.net> <3399111d-61fd-efb1-8f59-e3e246e588fc@oracle.com> Message-ID: Indeed, the emphasis I tried to make was about making it *implicit* unnamed On Wed, Mar 22, 2023, 14:37 Red IO wrote: > I think he suggests to get rid of the variable entirely like in his > example: > > try { > > } catch (Exception) //<- nothing here > { > } > > And try (new AutoclosableImpl()) {//<- no assignment to a variable > > } > > Great regards > RedIODev > > > On Wed, Mar 22, 2023, 13:19 Brian Goetz wrote: > >> >> >> On 3/21/2023 5:39 PM, Holo The Sage Wolf wrote: >> >> Is there a reason not to allow implicit Unnamed Variables in >> try-with-resources and Catch-block? >> >> >> The JEP already describes these cases: >> >> Unnamed variables >> >> The following kinds of declarations can introduce either a named variable >> (denoted by an identifier) or an unnamed variable (denoted by an >> underscore): >> >> - A local variable declaration statement in a block (JLS 14.4.2), >> - A resource specification of a try-with-resources statement (JLS >> 14.20.3), >> - The header of a basic for statement (JLS 14.14.1), >> - The header of an enhanced for loop (JLS 14.14.2), >> - An exception parameter of a catch block (JLS 14.20), and >> - A formal parameter of a lambda expression (JLS 15.27.1). >> >> >> Try with resources, and catch blocks, are listed as places where you can >> use unnamed variables. Am I undersatnding your question properly? >> >> >> >> To allow: >> >> try (Scope.open()) { >> ... >> } >> --- >> try { ... } >> catch (Exception) { ... } >> >> to be implicitly >> >> try (var _ = Scope.open()) { >> ... >> } >> --- >> try { ... } >> catch (Exception _) { ... } >> >> Unlike a normal expression, and expression on the RHS of a >> try-with-resources variable has an explicit context, there is no fear of >> someone forgetting the result of the expression by mistake. >> >> In a Catch block the exception is uniquely accessible through the >> variable declared in the declaration of the block, so there is no fear that >> someone will get confused by this implicitness. >> >> This implicitness will make stuff like logging context, which is far from >> a niche use case, much more elegant. >> >> On Tue, Mar 21, 2023 at 9:17?PM Mark Reinhold >> wrote: >> >>> https://openjdk.org/jeps/443 >>> >>> Summary: Enhance the Java language with unnamed patterns, which match >>> a record component without stating the component's name or type, >>> and unnamed variables, which can be initialized but not used. Both >>> are denoted by an underscore character, _. This is a preview language >>> feature. >>> >>> - Mark >> >> >> >> -- >> Holo The Wise Wolf Of Yoitsu >> >> >> -------------- next part -------------- An HTML attachment was scrubbed... URL: From naokikishida at gmail.com Wed Mar 22 18:13:47 2023 From: naokikishida at gmail.com (kishida naoki) Date: Thu, 23 Mar 2023 03:13:47 +0900 Subject: Record Pattern with swich has something wrong Message-ID: On running Nested Record+Switch code, java command throws error. condition is: switch has 2 case clauses. 1 case + default has no problem. The `Type` places 2nd position. `Product(Type type, String name)` has no problem. sealed does not affect. The code is below. ``` public class PatternSample { sealed interface Type { record Bulk(int price) implements Type {} record Packed(int price) implements Type {} } record Product(String name, Type type) {} public static void main(String[] args) { Product item = new Product("meat", new Type.Bulk(250)); int total = switch(item) { case Product(var n, Type.Packed(int price)) -> price; case Product(var n, Type.Bulk(int price)) -> price * 2; }; System.out.println(total); } } ``` The error is below. ``` >java --enable-preview PatternSample Error: Unable to initialize main class PatternSample Caused by: java.lang.VerifyError: Bad local variable type Exception Details: Location: PatternSample.main([Ljava/lang/String;)V @155: iload Reason: Type top (current frame, locals[6]) is not assignable to integer Current Frame: bci: @155 flags: { } locals: { '[Ljava/lang/String;', 'PatternSample$Product', top, 'PatternSample$Product', integer, 'PatternSample$Product', top, top, top, 'java/lang/String', integer } stack: { } Bytecode: 0000000: bb00 0759 1209 bb00 0b59 1100 fab7 000d 0000010: b700 104c 2b59 b800 1357 4e03 3604 2d15 0000020: 04ba 0019 0000 ab00 0000 007c 0000 0001 0000030: 0000 0000 0000 0012 2d3a 0519 05b6 001d 0000040: 3a09 0336 0a19 0915 0aba 0021 0000 ab00 0000050: 0000 004d 0000 0002 ffff ffff 0000 004d 0000060: 0000 0000 0000 001a 1909 3a06 1905 b600 0000070: 223a 0b19 0bc1 0026 9900 1819 0bc0 0026 0000080: 3a07 1907 b600 2836 0b15 0b36 08a7 0009 0000090: 0436 0aa7 ffb2 1508 a700 0b15 0605 68a7 00000a0: 0004 083d b200 2c1c b600 32a7 0011 4ebb 00000b0: 0039 592d b600 3b2d b700 3ebf b1 Exception Handler Table: bci [61, 64] => handler: 174 bci [110, 113] => handler: 174 bci [132, 135] => handler: 174 Stackmap Table: full_frame(@30,{Object[#72],Object[#7],Top,Object[#7],Integer},{}) same_frame(@56) full_frame(@69,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Top,Top,Top,Object[#74],Integer},{}) same_frame(@104) full_frame(@144,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Object[#74],Top,Top,Object[#74],Integer},{}) full_frame(@150,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Object[#74],Object[#38],Integer,Object[#74],Integer},{}) full_frame(@155,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Top,Top,Top,Object[#74],Integer},{}) full_frame(@162,{Object[#72],Object[#7],Top,Object[#7],Integer},{}) full_frame(@163,{Object[#72],Object[#7]},{Integer}) full_frame(@174,{Object[#72]},{Object[#55]}) append_frame(@188,Object[#7],Integer) ``` -- Naoki Kishida -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Mar 22 18:22:55 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 22 Mar 2023 14:22:55 -0400 Subject: Record Pattern with swich has something wrong In-Reply-To: References: Message-ID: <7bc4b1f8-fe5b-f0ca-6f81-d4a3d2f8bd23@oracle.com> Thanks for the bug report! We'll look into it. On 3/22/2023 2:13 PM, kishida naoki wrote: > On running Nested Record+Switch code, java command throws error. > condition is: > ? switch has 2 case clauses. 1 case + default has no problem. > ? The `Type` places 2nd position. `Product(Type type, String name)` > has no problem. > ? sealed does not affect. > > The code is below. > > ``` > public class PatternSample { > ? sealed interface Type { > ? ? record Bulk(int price) implements Type {} > ? ? record Packed(int price) implements Type {} > ? } > > ? record Product(String name, Type type) {} > > ? public static void main(String[] args) { > ? ? Product item = new Product("meat", new Type.Bulk(250)); > > ? ? int total = switch(item) { > ? ? ? ? case Product(var n, Type.Packed(int price)) > ? ? ? ? ? ? ? ? ?-> price; > ? ? ? ? case Product(var n, Type.Bulk(int price)) > ? ? ? ? ? ? ? ? ?-> price * 2; > ? ? ? }; > ? ? System.out.println(total); > ? } > } > ``` > > The error is below. > > ``` > >java --enable-preview PatternSample > Error: Unable to initialize main class PatternSample > Caused by: java.lang.VerifyError: Bad local variable type > Exception Details: > ? Location: > ? ? PatternSample.main([Ljava/lang/String;)V @155: iload > ? Reason: > ? ? Type top (current frame, locals[6]) is not assignable to integer > ? Current Frame: > ? ? bci: @155 > ? ? flags: { } > ? ? locals: { '[Ljava/lang/String;', 'PatternSample$Product', top, > 'PatternSample$Product', integer, 'PatternSample$Product', top, top, > top, 'java/lang/String', integer } > ? ? stack: { } > ? Bytecode: > ? ? 0000000: bb00 0759 1209 bb00 0b59 1100 fab7 000d > ? ? 0000010: b700 104c 2b59 b800 1357 4e03 3604 2d15 > ? ? 0000020: 04ba 0019 0000 ab00 0000 007c 0000 0001 > ? ? 0000030: 0000 0000 0000 0012 2d3a 0519 05b6 001d > ? ? 0000040: 3a09 0336 0a19 0915 0aba 0021 0000 ab00 > ? ? 0000050: 0000 004d 0000 0002 ffff ffff 0000 004d > ? ? 0000060: 0000 0000 0000 001a 1909 3a06 1905 b600 > ? ? 0000070: 223a 0b19 0bc1 0026 9900 1819 0bc0 0026 > ? ? 0000080: 3a07 1907 b600 2836 0b15 0b36 08a7 0009 > ? ? 0000090: 0436 0aa7 ffb2 1508 a700 0b15 0605 68a7 > ? ? 00000a0: 0004 083d b200 2c1c b600 32a7 0011 4ebb > ? ? 00000b0: 0039 592d b600 3b2d b700 3ebf b1 > ? Exception Handler Table: > ? ? bci [61, 64] => handler: 174 > ? ? bci [110, 113] => handler: 174 > ? ? bci [132, 135] => handler: 174 > ? Stackmap Table: > full_frame(@30,{Object[#72],Object[#7],Top,Object[#7],Integer},{}) > ? ? same_frame(@56) > full_frame(@69,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Top,Top,Top,Object[#74],Integer},{}) > ? ? same_frame(@104) > full_frame(@144,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Object[#74],Top,Top,Object[#74],Integer},{}) > full_frame(@150,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Object[#74],Object[#38],Integer,Object[#74],Integer},{}) > full_frame(@155,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Top,Top,Top,Object[#74],Integer},{}) > full_frame(@162,{Object[#72],Object[#7],Top,Object[#7],Integer},{}) > ? ? full_frame(@163,{Object[#72],Object[#7]},{Integer}) > ? ? full_frame(@174,{Object[#72]},{Object[#55]}) > ? ? append_frame(@188,Object[#7],Integer) > ``` > > -- > Naoki Kishida -------------- next part -------------- An HTML attachment was scrubbed... URL: From angelos.bimpoudis at oracle.com Thu Mar 23 15:41:32 2023 From: angelos.bimpoudis at oracle.com (Angelos Bimpoudis) Date: Thu, 23 Mar 2023 15:41:32 +0000 Subject: Record Pattern with swich has something wrong In-Reply-To: <7bc4b1f8-fe5b-f0ca-6f81-d4a3d2f8bd23@oracle.com> References: <7bc4b1f8-fe5b-f0ca-6f81-d4a3d2f8bd23@oracle.com> Message-ID: Hello! Thx for reaching out. It works on mainline JDK. The fix was introduced via https://bugs.openjdk.org/browse/JDK-8302202 that is already backported so it will be available soon to JDK 20 users. Best, Angelos ________________________________ From: amber-dev on behalf of Brian Goetz Sent: 22 March 2023 19:22 To: kishida naoki ; amber-dev at openjdk.org Subject: Re: Record Pattern with swich has something wrong Thanks for the bug report! We'll look into it. On 3/22/2023 2:13 PM, kishida naoki wrote: On running Nested Record+Switch code, java command throws error. condition is: switch has 2 case clauses. 1 case + default has no problem. The `Type` places 2nd position. `Product(Type type, String name)` has no problem. sealed does not affect. The code is below. ``` public class PatternSample { sealed interface Type { record Bulk(int price) implements Type {} record Packed(int price) implements Type {} } record Product(String name, Type type) {} public static void main(String[] args) { Product item = new Product("meat", new Type.Bulk(250)); int total = switch(item) { case Product(var n, Type.Packed(int price)) -> price; case Product(var n, Type.Bulk(int price)) -> price * 2; }; System.out.println(total); } } ``` The error is below. ``` >java --enable-preview PatternSample Error: Unable to initialize main class PatternSample Caused by: java.lang.VerifyError: Bad local variable type Exception Details: Location: PatternSample.main([Ljava/lang/String;)V @155: iload Reason: Type top (current frame, locals[6]) is not assignable to integer Current Frame: bci: @155 flags: { } locals: { '[Ljava/lang/String;', 'PatternSample$Product', top, 'PatternSample$Product', integer, 'PatternSample$Product', top, top, top, 'java/lang/String', integer } stack: { } Bytecode: 0000000: bb00 0759 1209 bb00 0b59 1100 fab7 000d 0000010: b700 104c 2b59 b800 1357 4e03 3604 2d15 0000020: 04ba 0019 0000 ab00 0000 007c 0000 0001 0000030: 0000 0000 0000 0012 2d3a 0519 05b6 001d 0000040: 3a09 0336 0a19 0915 0aba 0021 0000 ab00 0000050: 0000 004d 0000 0002 ffff ffff 0000 004d 0000060: 0000 0000 0000 001a 1909 3a06 1905 b600 0000070: 223a 0b19 0bc1 0026 9900 1819 0bc0 0026 0000080: 3a07 1907 b600 2836 0b15 0b36 08a7 0009 0000090: 0436 0aa7 ffb2 1508 a700 0b15 0605 68a7 00000a0: 0004 083d b200 2c1c b600 32a7 0011 4ebb 00000b0: 0039 592d b600 3b2d b700 3ebf b1 Exception Handler Table: bci [61, 64] => handler: 174 bci [110, 113] => handler: 174 bci [132, 135] => handler: 174 Stackmap Table: full_frame(@30,{Object[#72],Object[#7],Top,Object[#7],Integer},{}) same_frame(@56) full_frame(@69,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Top,Top,Top,Object[#74],Integer},{}) same_frame(@104) full_frame(@144,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Object[#74],Top,Top,Object[#74],Integer},{}) full_frame(@150,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Object[#74],Object[#38],Integer,Object[#74],Integer},{}) full_frame(@155,{Object[#72],Object[#7],Top,Object[#7],Integer,Object[#7],Top,Top,Top,Object[#74],Integer},{}) full_frame(@162,{Object[#72],Object[#7],Top,Object[#7],Integer},{}) full_frame(@163,{Object[#72],Object[#7]},{Integer}) full_frame(@174,{Object[#72]},{Object[#55]}) append_frame(@188,Object[#7],Integer) ``` -- Naoki Kishida -------------- next part -------------- An HTML attachment was scrubbed... URL: From blaisebass at gmail.com Fri Mar 24 14:58:51 2023 From: blaisebass at gmail.com (Blaise B.) Date: Fri, 24 Mar 2023 15:58:51 +0100 Subject: Record Pattern with swich has something wrong Message-ID: Hello! I could still reproduce the bug on JDK 21 EA (openjdk-21-ea+15_windows-x64_bin) with a slightly different code: public class Main { record Foo(Object first, Object second) {} public static void main(String[] args) { Foo myfoo = new Foo(42, "millis"); switch (myfoo) { case Foo(Integer d, String s) -> System.out.println("got integer and string: " + d + ", " + s); case Foo(Object fs, Object sc) -> System.out.println("values: " + fs + ", " + sc); } } } >javac --release 21 --enable-preview Main.java Note: Main.java uses preview features of Java SE 21. Note: Recompile with -Xlint:preview for details. >java --enable-preview Main Error: Unable to initialize main class Main Caused by: java.lang.VerifyError: Bad local variable type Exception Details: Location: Main.main([Ljava/lang/String;)V @192: aload Reason: Type top (current frame, locals[8]) is not assignable to reference type Current Frame: bci: @192 flags: { } locals: { '[Ljava/lang/String;', 'Main$Foo', 'Main$Foo', integer, 'Main$Foo', top, top, 'java/lang/Object', top, 'java/lang/Object', integer } stack: { 'java/io/PrintStream', 'java/lang/String' } Bytecode: 0000000: bb00 0759 05b8 0009 120f b700 114c 2b59 0000010: b800 1457 4d03 3e2c 1dba 001a 0000 ab00 0000020: 0000 0012 0000 0001 0000 0000 0000 001c 0000030: bb00 1e59 0101 b700 20bf 2c3a 0419 04b6 0000040: 0023 3a09 0336 0a19 0915 0aba 0027 0000 0000050: aa00 0000 0000 0080 ffff ffff 0000 0001 0000060: 0000 0054 0000 001c 0000 0054 1909 c000 0000070: 0a3a 0519 04b6 0028 3a0b 190b c100 2b99 0000080: 000d 190b c000 2b3a 06a7 0009 0436 0aa7 0000090: ffb8 b200 2d19 0519 06ba 0033 0000 b600 00000a0: 37a7 0034 1909 3a07 1907 c600 0e19 04b6 00000b0: 0028 3a0b 190b 3a08 b200 2d19 07b8 003d 00000c0: 1908 b800 3dba 0040 0000 b600 37a7 0008 00000d0: 043e a7ff 45a7 0011 4dbb 001e 592c b600 00000e0: 452c b700 20bf b1 Exception Handler Table: bci [63, 66] => handler: 216 bci [117, 120] => handler: 216 bci [175, 178] => handler: 216 Stackmap Table: append_frame(@23,Object[#7],Object[#7],Integer) same_frame(@48) same_frame(@58) full_frame(@71,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Top,Top,Object[#2],Integer},{}) same_frame(@108) full_frame(@140,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Object[#10],Top,Top,Top,Object[#2],Integer},{}) full_frame(@146,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Object[#10],Object[#43],Top,Top,Object[#2],Integer},{}) full_frame(@164,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Top,Top,Object[#2],Integer},{}) full_frame(@184,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Object[#2],Top,Object[#2],Integer},{}) full_frame(@208,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Top,Top,Object[#2],Integer},{}) full_frame(@213,{Object[#80],Object[#7]},{}) full_frame(@216,{Object[#80]},{Object[#67]}) append_frame(@230,Object[#7]) -------------- next part -------------- An HTML attachment was scrubbed... URL: From holo3146 at gmail.com Fri Mar 24 15:10:18 2023 From: holo3146 at gmail.com (Holo The Sage Wolf) Date: Fri, 24 Mar 2023 18:10:18 +0300 Subject: Record Pattern with swich has something wrong In-Reply-To: References: Message-ID: It is odd that something like this is not a test-case already On Fri, Mar 24, 2023, 17:59 Blaise B. wrote: > Hello! > > I could still reproduce the bug on JDK 21 EA > (openjdk-21-ea+15_windows-x64_bin) with a slightly different code: > > public class Main { > record Foo(Object first, Object second) {} > > public static void main(String[] args) { > Foo myfoo = new Foo(42, "millis"); > > switch (myfoo) { > case Foo(Integer d, String s) -> > System.out.println("got integer and string: " + d + ", " + s); > case Foo(Object fs, Object sc) -> > System.out.println("values: " + fs + ", " + sc); > } > } > } > > > >javac --release 21 --enable-preview Main.java > Note: Main.java uses preview features of Java SE 21. > Note: Recompile with -Xlint:preview for details. > > > >java --enable-preview Main > Error: Unable to initialize main class Main > Caused by: java.lang.VerifyError: Bad local variable type > Exception Details: > Location: > Main.main([Ljava/lang/String;)V @192: aload > Reason: > Type top (current frame, locals[8]) is not assignable to reference type > Current Frame: > bci: @192 > flags: { } > locals: { '[Ljava/lang/String;', 'Main$Foo', 'Main$Foo', integer, > 'Main$Foo', top, top, 'java/lang/Object', top, 'java/lang/Object', integer } > stack: { 'java/io/PrintStream', 'java/lang/String' } > Bytecode: > 0000000: bb00 0759 05b8 0009 120f b700 114c 2b59 > 0000010: b800 1457 4d03 3e2c 1dba 001a 0000 ab00 > 0000020: 0000 0012 0000 0001 0000 0000 0000 001c > 0000030: bb00 1e59 0101 b700 20bf 2c3a 0419 04b6 > 0000040: 0023 3a09 0336 0a19 0915 0aba 0027 0000 > 0000050: aa00 0000 0000 0080 ffff ffff 0000 0001 > 0000060: 0000 0054 0000 001c 0000 0054 1909 c000 > 0000070: 0a3a 0519 04b6 0028 3a0b 190b c100 2b99 > 0000080: 000d 190b c000 2b3a 06a7 0009 0436 0aa7 > 0000090: ffb8 b200 2d19 0519 06ba 0033 0000 b600 > 00000a0: 37a7 0034 1909 3a07 1907 c600 0e19 04b6 > 00000b0: 0028 3a0b 190b 3a08 b200 2d19 07b8 003d > 00000c0: 1908 b800 3dba 0040 0000 b600 37a7 0008 > 00000d0: 043e a7ff 45a7 0011 4dbb 001e 592c b600 > 00000e0: 452c b700 20bf b1 > Exception Handler Table: > bci [63, 66] => handler: 216 > bci [117, 120] => handler: 216 > bci [175, 178] => handler: 216 > Stackmap Table: > append_frame(@23,Object[#7],Object[#7],Integer) > same_frame(@48) > same_frame(@58) > > full_frame(@71,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Top,Top,Object[#2],Integer},{}) > same_frame(@108) > > full_frame(@140,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Object[#10],Top,Top,Top,Object[#2],Integer},{}) > > full_frame(@146,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Object[#10],Object[#43],Top,Top,Object[#2],Integer},{}) > > full_frame(@164,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Top,Top,Object[#2],Integer},{}) > > full_frame(@184,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Object[#2],Top,Object[#2],Integer},{}) > > full_frame(@208,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Top,Top,Object[#2],Integer},{}) > full_frame(@213,{Object[#80],Object[#7]},{}) > full_frame(@216,{Object[#80]},{Object[#67]}) > append_frame(@230,Object[#7]) > -------------- next part -------------- An HTML attachment was scrubbed... URL: From angelos.bimpoudis at oracle.com Fri Mar 24 15:46:08 2023 From: angelos.bimpoudis at oracle.com (Angelos Bimpoudis) Date: Fri, 24 Mar 2023 15:46:08 +0000 Subject: Record Pattern with swich has something wrong In-Reply-To: References: Message-ID: Thanks for the new failing test case. While the previous was passing in mainline, this one is indeed failing in both 20 and mainline. We will look into it. Aggelos ________________________________ From: amber-dev on behalf of Holo The Sage Wolf Sent: 24 March 2023 16:10 To: Blaise B. ; amber-dev Subject: Re: Record Pattern with swich has something wrong It is odd that something like this is not a test-case already On Fri, Mar 24, 2023, 17:59 Blaise B. > wrote: Hello! I could still reproduce the bug on JDK 21 EA (openjdk-21-ea+15_windows-x64_bin) with a slightly different code: public class Main { record Foo(Object first, Object second) {} public static void main(String[] args) { Foo myfoo = new Foo(42, "millis"); switch (myfoo) { case Foo(Integer d, String s) -> System.out.println("got integer and string: " + d + ", " + s); case Foo(Object fs, Object sc) -> System.out.println("values: " + fs + ", " + sc); } } } >javac --release 21 --enable-preview Main.java Note: Main.java uses preview features of Java SE 21. Note: Recompile with -Xlint:preview for details. >java --enable-preview Main Error: Unable to initialize main class Main Caused by: java.lang.VerifyError: Bad local variable type Exception Details: Location: Main.main([Ljava/lang/String;)V @192: aload Reason: Type top (current frame, locals[8]) is not assignable to reference type Current Frame: bci: @192 flags: { } locals: { '[Ljava/lang/String;', 'Main$Foo', 'Main$Foo', integer, 'Main$Foo', top, top, 'java/lang/Object', top, 'java/lang/Object', integer } stack: { 'java/io/PrintStream', 'java/lang/String' } Bytecode: 0000000: bb00 0759 05b8 0009 120f b700 114c 2b59 0000010: b800 1457 4d03 3e2c 1dba 001a 0000 ab00 0000020: 0000 0012 0000 0001 0000 0000 0000 001c 0000030: bb00 1e59 0101 b700 20bf 2c3a 0419 04b6 0000040: 0023 3a09 0336 0a19 0915 0aba 0027 0000 0000050: aa00 0000 0000 0080 ffff ffff 0000 0001 0000060: 0000 0054 0000 001c 0000 0054 1909 c000 0000070: 0a3a 0519 04b6 0028 3a0b 190b c100 2b99 0000080: 000d 190b c000 2b3a 06a7 0009 0436 0aa7 0000090: ffb8 b200 2d19 0519 06ba 0033 0000 b600 00000a0: 37a7 0034 1909 3a07 1907 c600 0e19 04b6 00000b0: 0028 3a0b 190b 3a08 b200 2d19 07b8 003d 00000c0: 1908 b800 3dba 0040 0000 b600 37a7 0008 00000d0: 043e a7ff 45a7 0011 4dbb 001e 592c b600 00000e0: 452c b700 20bf b1 Exception Handler Table: bci [63, 66] => handler: 216 bci [117, 120] => handler: 216 bci [175, 178] => handler: 216 Stackmap Table: append_frame(@23,Object[#7],Object[#7],Integer) same_frame(@48) same_frame(@58) full_frame(@71,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Top,Top,Object[#2],Integer},{}) same_frame(@108) full_frame(@140,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Object[#10],Top,Top,Top,Object[#2],Integer},{}) full_frame(@146,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Object[#10],Object[#43],Top,Top,Object[#2],Integer},{}) full_frame(@164,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Top,Top,Object[#2],Integer},{}) full_frame(@184,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Object[#2],Top,Object[#2],Integer},{}) full_frame(@208,{Object[#80],Object[#7],Object[#7],Integer,Object[#7],Top,Top,Top,Top,Object[#2],Integer},{}) full_frame(@213,{Object[#80],Object[#7]},{}) full_frame(@216,{Object[#80]},{Object[#67]}) append_frame(@230,Object[#7]) -------------- next part -------------- An HTML attachment was scrubbed... URL: From robbepincket at live.be Sat Mar 25 21:45:35 2023 From: robbepincket at live.be (Robbe Pincket) Date: Sat, 25 Mar 2023 21:45:35 +0000 Subject: Assignments to finals in switch guards Message-ID: Hi all It seems that the following code still compiles in Java 20. ```java class Test { public static void main(String[] args) { System.out.println("Got: " + Test(17)); } static boolean log(Object o) { System.out.println("Log:" + o); return false; } static String test(Object o) { final String s; switch (o) { case Integer i when (s = "a") != null && log(s) -> { return s; } case Integer i when (s = "b") != null && log(s) -> { return s; } case Integer i when (s = "c") != null && log(s) -> { return s; } case Object o1 when (s = "d") != null && log(s) || true -> { return s; } default -> { throw new IllegalStateException("Unreachable"); } } } } ``` The code above reassigns the final variable `s` multiple times and prints: ``` Log: a Log: b Log: c Log: d Got: d ``` Similarly the following code: ```java class Test { public static void main(String[] args) { System.out.println("Got: " + new Test(17).s); } boolean log(Object o) { System.out.println("Log: " + o + ", " + this.s); return false; } final String s; Test(Object o) { switch (o) { case Integer i when(s = "a") != null && log(s) -> { } case Integer i when(s = "b") != null && log(s) -> { } case Integer i when(s = "c") != null && log(s) -> { } case Object o1 when(s = "d") != null && log(s) || true -> { } default -> { throw new IllegalStateException("Unreachable"); } } } } ``` Also compiles and produces the following ``` Log: a, a Log: b, b Log: c, c Log: d, d Got: d ``` Showing the final field being reassigned multiple times. Right before sending this email, I just remembered that the first example shouldn't compile, as in each `return s;` the `s` variable shouldn't be considered to be definite assigned. Kind regards Robbe Pincket -------------- next part -------------- An HTML attachment was scrubbed... URL: From krka at spotify.com Tue Mar 28 12:37:13 2023 From: krka at spotify.com (Kristofer Karlsson) Date: Tue, 28 Mar 2023 14:37:13 +0200 Subject: Language feature to support Fluent Syntax for Static Method Calls Message-ID: Hi, I am new to this list so apologies if I'm missing any guidelines to follow. This feature idea includes a specific solution, but I think the feature (some way to support a more fluent code flow with methods that are not part of the object) is more important than the solution (Allowing a new syntax for static method calls) so even if the solution ends up being rejected, I hope the feature itself does not have to be. I have tried searching for existing JEPs that roughly matched this feature idea but could not find anything. Before trying to create an actual JEP, I thought it would be useful to know if this is a reasonable idea at all. The closest thing I've been able to find is this stackoverflow post[1] where extension methods are discussed. However, I'm thinking about this problem in terms of enabling a more fluent code style at the use-site without changing any semantics, and not in terms of designing and controlling APIs (for which the existing interface abstractions already work very well). [1]: https://stackoverflow.com/questions/29466427/what-was-the-design-consideration-of-not-allowing-use-site-injection-of-extensio/29494337#29494337 Best regards Kristofer --- Title: Fluent Syntax for Static Method Calls Author: Kristofer Karlsson Created: 2023/03/27 Type: Feature State: Draft Exposure: Open Component: specification?/?language Scope: SE Discussion: amber dash dev at openjdk dot java dot net Template: 1.0 ## Summary Enhance the language by allowing static method calls to be used in a fluent way. ## Goals Enhance the language and compiler to support invoking static methods in a fluent way. This would be a purely ergonomic change to help developers to write code that is easier to read, write and maintain without breaking any existing code and without requiring any changes to the bytecode or the runtime. Fluent in this context refers to https://en.wikipedia.org/wiki/Fluent_interface ## Motivation Given the JDK additions of streams and classes such as CompletableFuture, and already existing patterns of using builders and operating on immutable types, writing code in a fluent style has become common, and is considered best practice in many cases. For streams you would do something like: list .stream() .map(x -> x + 1) .limit(10) .collect(Collectors.toSet()) and with CompletableFuture you would do something like: CompletableFuture.completedFuture("hello") .thenApply(s -> s + " world") .exceptionally(e -> "An error occurred") .thenRun(this::someCallback); These classes and interfaces are out of our control - they are part of the JDK - so we can not extend them to add other potentially useful methods such as Stream.parallel(executor), Stream.toSet(), CompletableFuture.handleCompose(func), CompletableFuture.orTimeout(time, unit, executor). We can create static methods that operate on such classes and return new instances, but chaining such calls together becomes clunky. Here's a (somewhat contrived) example of what it would look like today vs what it would look like with the proposal in place: toSet(parallel(list.stream(), myExecutor) .map(x -> x + 1) .limit(10)) compared to: import static MyStream.parallel; import static MyStream.toSet; list.stream() .parallel(myExecutor) .map(x -> x + 1) .limit(10) .toSet() And for CompletableFuture: orTimeout( handleCompose( flatten( CompletableFuture.completedFuture(null) .exceptionally(e -> new CompletableFuture<>())), (v, e) -> ...), 10, TimeUnit.SECONDS, myExecutor) compared to: import static MyFutures.flatten; import static MyFutures.handleCompose; import static MyFutures.orTimeout; CompletableFuture.completedFuture(null) .exceptionally(e -> new CompletableFuture<>()) .flatten() .handleCompose((v, e) -> ...) .orTimeout(10, TimeUnit.SECONDS, myExecutor) Note: CompletableFuture.exceptionallyCompose is an example of a method that was initially missing but later added due to being convenient, even though it could have been implemented as a simple chain of calls: future.thenApply(CompletableFuture::new) .exceptionally(e -> func(e)) .thenCompose(Function.identity()) # Description Currently static method calls look like: ClassName.methodName(arg1, arg2, ...); Combined with a static static import statement they can also look like this: import static ClassName.methodName; methodName(arg1, arg2, ...); The compiler could be modified to also recognize this form: import static ClassName.methodName; arg1.methodName(arg2, ...); For simplicity, this would only be allowed for statically imported methods. This means that you could invoke Arrays.fill() as: import static java.util.Arrays.fill; int[] array = ...; array.fill(0); ## Out of scope Technically, this feature could also be allowed for primitives. This would mean that you could invoke Math.abs(-1L) as (-1L).abs() and Math.exp(2.0, 3.0) as (2.0).exp(3.0). However, this may introduce extra complexity for the compiler to handle, so for the purpose of simplifying the JEP, this is out of scope. ## Compatibility All existing valid code will continue to be valid and behave exactly as before. ## Risks If new code introduces usage of this new method invocation, there could be a subtle change to the method resolution if the argument type introduces a method that matches the signature. Recompiling the class after the argument type dependency has been updated would lead to a different method resolution. However, this is already true for a similar case: public class A { public static void main(String[] args) { (new B()).theMethod(); } } public class B { static void theMethod() { } } will show that invoking B.theMethod() resolves to invokestatic: 8: invokestatic #4 // Method B.theMethod:()V If B is then changed to: public class B { void theMethod() { } } and we recompile A, we get: 7: invokevirtual #4 // Method B.theMethod:()V If we don't recompile A, we get this error instead: Exception in thread "main" java.lang.IncompatibleClassChangeError: Expected static method 'void B.theMethod()' at A.main(A.java:3) From holo3146 at gmail.com Tue Mar 28 14:55:00 2023 From: holo3146 at gmail.com (Holo The Sage Wolf) Date: Tue, 28 Mar 2023 17:55:00 +0300 Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: References: Message-ID: What you are describing is Universal Function Call Syntax, and not Fluent Syntax. Fluent syntax is the pattern of ending a function with "return this" or "return stage" to allow, well, a fluent API. Universal Function Call Syntax is having multiple different syntaxes to call a function. The particular cases you are describing are solvable using Extension Methods (i.e. Poor's man UFCS), and you can find an implementation of this in Lombok [1] (note that Lombok uses several hacks for this). [1] https://projectlombok.org/features/experimental/ExtensionMethod On Tue, Mar 28, 2023, 15:39 Kristofer Karlsson wrote: > Hi, I am new to this list so apologies if I'm missing any guidelines to > follow. > > This feature idea includes a specific solution, but I think the > feature (some way to support a more fluent code flow with methods that > are not part of the object) is more important than the solution > (Allowing a new syntax for static method calls) so even if the > solution ends up being rejected, I hope the feature itself does not > have to be. > > I have tried searching for existing JEPs that roughly matched this > feature idea but could not find anything. Before trying to create an > actual JEP, I thought it would be useful to know if this is a > reasonable idea at all. The closest thing I've been able to find is > this stackoverflow post[1] where extension methods are discussed. > However, I'm thinking about this problem in terms of enabling a more > fluent code style at the use-site without changing any semantics, and > not in terms of designing and controlling APIs (for which the existing > interface abstractions already work very well). > > [1]: > https://stackoverflow.com/questions/29466427/what-was-the-design-consideration-of-not-allowing-use-site-injection-of-extensio/29494337#29494337 > > Best regards > Kristofer > > --- > > Title: Fluent Syntax for Static Method Calls > Author: Kristofer Karlsson > Created: 2023/03/27 > Type: Feature > State: Draft > Exposure: Open > Component: specification / language > Scope: SE > Discussion: amber dash dev at openjdk dot java dot net > Template: 1.0 > > ## Summary > > Enhance the language by allowing static method calls to be used in a > fluent way. > > ## Goals > > Enhance the language and compiler to support invoking static methods > in a fluent way. This would be a purely ergonomic change to help > developers to write code that is easier to read, write and maintain > without breaking any existing code and without requiring any changes > to the bytecode or the runtime. > > Fluent in this context refers to > https://en.wikipedia.org/wiki/Fluent_interface > > ## Motivation > > Given the JDK additions of streams and classes such as CompletableFuture, > and > already existing patterns of using builders and operating on immutable > types, > writing code in a fluent style has become common, and is considered best > practice in many cases. > > For streams you would do something like: > list > .stream() > .map(x -> x + 1) > .limit(10) > .collect(Collectors.toSet()) > > and with CompletableFuture you would do something like: > CompletableFuture.completedFuture("hello") > .thenApply(s -> s + " world") > .exceptionally(e -> "An error occurred") > .thenRun(this::someCallback); > > These classes and interfaces are out of our control - they are part of the > JDK - > so we can not extend them to add other potentially useful methods such > as Stream.parallel(executor), Stream.toSet(), > CompletableFuture.handleCompose(func), > CompletableFuture.orTimeout(time, unit, executor). > > We can create static methods that operate on such classes and return > new instances, > but chaining such calls together becomes clunky. Here's a (somewhat > contrived) > example of what it would look like today vs what it would look like with > the proposal in place: > toSet(parallel(list.stream(), myExecutor) > .map(x -> x + 1) > .limit(10)) > > compared to: > import static MyStream.parallel; > import static MyStream.toSet; > > list.stream() > .parallel(myExecutor) > .map(x -> x + 1) > .limit(10) > .toSet() > > > And for CompletableFuture: > orTimeout( > handleCompose( > flatten( > CompletableFuture.completedFuture(null) > .exceptionally(e -> new CompletableFuture<>())), > (v, e) -> ...), > 10, TimeUnit.SECONDS, myExecutor) > > compared to: > import static MyFutures.flatten; > import static MyFutures.handleCompose; > import static MyFutures.orTimeout; > > CompletableFuture.completedFuture(null) > .exceptionally(e -> new CompletableFuture<>()) > .flatten() > .handleCompose((v, e) -> ...) > .orTimeout(10, TimeUnit.SECONDS, myExecutor) > > Note: CompletableFuture.exceptionallyCompose is an example of a method > that was > initially missing but later added due to being convenient, even though it > could > have been implemented as a simple chain of calls: > future.thenApply(CompletableFuture::new) > .exceptionally(e -> func(e)) > .thenCompose(Function.identity()) > > # Description > > Currently static method calls look like: > ClassName.methodName(arg1, arg2, ...); > > Combined with a static static import statement they can also look like > this: > import static ClassName.methodName; > methodName(arg1, arg2, ...); > > The compiler could be modified to also recognize this form: > import static ClassName.methodName; > arg1.methodName(arg2, ...); > > For simplicity, this would only be allowed for statically imported methods. > This means that you could invoke Arrays.fill() as: > import static java.util.Arrays.fill; > int[] array = ...; > array.fill(0); > > > ## Out of scope > Technically, this feature could also be allowed for primitives. This > would mean that > you could invoke Math.abs(-1L) as (-1L).abs() and Math.exp(2.0, 3.0) > as (2.0).exp(3.0). > > However, this may introduce extra complexity for the compiler to > handle, so for the purpose > of simplifying the JEP, this is out of scope. > > ## Compatibility > > All existing valid code will continue to be valid and behave exactly as > before. > > ## Risks > > If new code introduces usage of this new method invocation, there could be > a > subtle change to the method resolution if the argument type introduces a > method that matches the signature. Recompiling the class after the > argument type > dependency has been updated would lead to a different method resolution. > > However, this is already true for a similar case: > > public class A { > public static void main(String[] args) { > (new B()).theMethod(); > } > } > > public class B { > static void theMethod() { } > } > > will show that invoking B.theMethod() resolves to invokestatic: > 8: invokestatic #4 // Method B.theMethod:()V > > If B is then changed to: > public class B { > void theMethod() { } > } > > and we recompile A, we get: > 7: invokevirtual #4 // Method B.theMethod:()V > > If we don't recompile A, we get this error instead: > > Exception in thread "main" java.lang.IncompatibleClassChangeError: > Expected static method 'void B.theMethod()' > at A.main(A.java:3) > -------------- next part -------------- An HTML attachment was scrubbed... URL: From ron.pressler at oracle.com Tue Mar 28 15:44:53 2023 From: ron.pressler at oracle.com (Ron Pressler) Date: Tue, 28 Mar 2023 15:44:53 +0000 Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: References: Message-ID: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> The best and surest way to impact the evolution of Java is not by offering solutions, but by reporting problems. Java is a conservative language, i.e. we are more reluctant to add language features than other languages, and the bar for a language feature is relatively high: it should either solve a big problem or multiple small problems (the more magic the feature adds, the higher the bar). The problem you reported is that composing operations using static methods may be ?clunky?, but you also provided two specific examples (good!). Unsurprisingly, both concern ?functional pipelines? made up of higher-order operators. It?s hard to judge how big of a problem this clunkiness imposes in general, but before even thinking about whether or not this justifies a change to the language, we can study these examples more carefully. Composing asynchronous operations with CompletableFuture may indeed be clunky, but the syntactic aesthetics is the least of the problem. Context is lost, language control flow and exceptions don?t work as they normally do, debugging is very difficult and profiling is almost impossible. These problems are so big that they justified an bigger change, virtual threads, that allow you not only to compose operations using the language?s built-in composition, but also give back context, and work well with tooling. If you want nicer composition, consider using a virtual thread, and you?ll gain other benefits, too. As for streams, it is, indeed, aesthetically unpleasing at times to add user-provided operators to the pipeline. But that could be a result of a deeper inflexibility of streams, and making streams more flexible would yield higher dividends in this case than a new language feature. I would also suggest that: var s = parallel(list.stream(), myExecutor) .map(x -> x + 1) .limit(10); var mySet = toSet(s); is quite readable. The question of, ?can this code be written in a way that a reader will easily understand what it does??, is more important to us than the question, ?can this code be written in a way that the author finds pretty?? however tempting it is to add a feature that makes code looks pretty (and sometimes prettier may actually mean *harder* to understand). As usual, the main challenge is understanding what exactly is the problem here ? is this a specific issue with CF and Stream or something more general ? and if there is a general problem, what exactly is it, and does it justify a change to the language. Only after we answer that can we consider adding a language feature. ? Ron > On 28 Mar 2023, at 13:37, Kristofer Karlsson wrote: > > Hi, I am new to this list so apologies if I'm missing any guidelines to follow. > > This feature idea includes a specific solution, but I think the > feature (some way to support a more fluent code flow with methods that > are not part of the object) is more important than the solution > (Allowing a new syntax for static method calls) so even if the > solution ends up being rejected, I hope the feature itself does not > have to be. > > I have tried searching for existing JEPs that roughly matched this > feature idea but could not find anything. Before trying to create an > actual JEP, I thought it would be useful to know if this is a > reasonable idea at all. The closest thing I've been able to find is > this stackoverflow post[1] where extension methods are discussed. > However, I'm thinking about this problem in terms of enabling a more > fluent code style at the use-site without changing any semantics, and > not in terms of designing and controlling APIs (for which the existing > interface abstractions already work very well). > > [1]: https://stackoverflow.com/questions/29466427/what-was-the-design-consideration-of-not-allowing-use-site-injection-of-extensio/29494337#29494337 > > Best regards > Kristofer > > --- > > Title: Fluent Syntax for Static Method Calls > Author: Kristofer Karlsson > Created: 2023/03/27 > Type: Feature > State: Draft > Exposure: Open > Component: specification?/?language > Scope: SE > Discussion: amber dash dev at openjdk dot java dot net > Template: 1.0 > > ## Summary > > Enhance the language by allowing static method calls to be used in a fluent way. > > ## Goals > > Enhance the language and compiler to support invoking static methods > in a fluent way. This would be a purely ergonomic change to help > developers to write code that is easier to read, write and maintain > without breaking any existing code and without requiring any changes > to the bytecode or the runtime. > > Fluent in this context refers to https://en.wikipedia.org/wiki/Fluent_interface > > ## Motivation > > Given the JDK additions of streams and classes such as CompletableFuture, and > already existing patterns of using builders and operating on immutable types, > writing code in a fluent style has become common, and is considered best > practice in many cases. > > For streams you would do something like: > list > .stream() > .map(x -> x + 1) > .limit(10) > .collect(Collectors.toSet()) > > and with CompletableFuture you would do something like: > CompletableFuture.completedFuture("hello") > .thenApply(s -> s + " world") > .exceptionally(e -> "An error occurred") > .thenRun(this::someCallback); > > These classes and interfaces are out of our control - they are part of the JDK - > so we can not extend them to add other potentially useful methods such > as Stream.parallel(executor), Stream.toSet(), > CompletableFuture.handleCompose(func), > CompletableFuture.orTimeout(time, unit, executor). > > We can create static methods that operate on such classes and return > new instances, > but chaining such calls together becomes clunky. Here's a (somewhat contrived) > example of what it would look like today vs what it would look like with > the proposal in place: > toSet(parallel(list.stream(), myExecutor) > .map(x -> x + 1) > .limit(10)) > > compared to: > import static MyStream.parallel; > import static MyStream.toSet; > > list.stream() > .parallel(myExecutor) > .map(x -> x + 1) > .limit(10) > .toSet() > > > And for CompletableFuture: > orTimeout( > handleCompose( > flatten( > CompletableFuture.completedFuture(null) > .exceptionally(e -> new CompletableFuture<>())), > (v, e) -> ...), > 10, TimeUnit.SECONDS, myExecutor) > > compared to: > import static MyFutures.flatten; > import static MyFutures.handleCompose; > import static MyFutures.orTimeout; > > CompletableFuture.completedFuture(null) > .exceptionally(e -> new CompletableFuture<>()) > .flatten() > .handleCompose((v, e) -> ...) > .orTimeout(10, TimeUnit.SECONDS, myExecutor) > > Note: CompletableFuture.exceptionallyCompose is an example of a method that was > initially missing but later added due to being convenient, even though it could > have been implemented as a simple chain of calls: > future.thenApply(CompletableFuture::new) > .exceptionally(e -> func(e)) > .thenCompose(Function.identity()) > > # Description > > Currently static method calls look like: > ClassName.methodName(arg1, arg2, ...); > > Combined with a static static import statement they can also look like this: > import static ClassName.methodName; > methodName(arg1, arg2, ...); > > The compiler could be modified to also recognize this form: > import static ClassName.methodName; > arg1.methodName(arg2, ...); > > For simplicity, this would only be allowed for statically imported methods. > This means that you could invoke Arrays.fill() as: > import static java.util.Arrays.fill; > int[] array = ...; > array.fill(0); > > > ## Out of scope > Technically, this feature could also be allowed for primitives. This > would mean that > you could invoke Math.abs(-1L) as (-1L).abs() and Math.exp(2.0, 3.0) > as (2.0).exp(3.0). > > However, this may introduce extra complexity for the compiler to > handle, so for the purpose > of simplifying the JEP, this is out of scope. > > ## Compatibility > > All existing valid code will continue to be valid and behave exactly as before. > > ## Risks > > If new code introduces usage of this new method invocation, there could be a > subtle change to the method resolution if the argument type introduces a > method that matches the signature. Recompiling the class after the argument type > dependency has been updated would lead to a different method resolution. > > However, this is already true for a similar case: > > public class A { > public static void main(String[] args) { > (new B()).theMethod(); > } > } > > public class B { > static void theMethod() { } > } > > will show that invoking B.theMethod() resolves to invokestatic: > 8: invokestatic #4 // Method B.theMethod:()V > > If B is then changed to: > public class B { > void theMethod() { } > } > > and we recompile A, we get: > 7: invokevirtual #4 // Method B.theMethod:()V > > If we don't recompile A, we get this error instead: > > Exception in thread "main" java.lang.IncompatibleClassChangeError: > Expected static method 'void B.theMethod()' > at A.main(A.java:3) From brian.goetz at oracle.com Tue Mar 28 16:57:12 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 28 Mar 2023 12:57:12 -0400 Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: References: Message-ID: To Ron's excellent answer, I'll add a few more points about things already considered-and-rejected. ?- As the linked SO post indicates, we explicitly decided against extension methods on the basis that they are a _bad idea_.? This differs from many features that might be reasonable ideas, but just don't rise to the level of worth changing the language for.? Extension methods are a feature that explicitly subverts one of the important values of the platform -- among their many other weaknesses, such as poor discoverability and non-extensibility. ?- During the development of the streams API, we did consider the question of extensibility, and whether some additional support for chaining static operations was valuable, similar to the "thrush" operator in Clojure (http://blog.fogus.me/2010/09/28/thrush-in-clojure-redux/). This is a more explicit syntax for "call this thing with the LHS as the first argument", something like `stream |> map(...) |> filter(...)`.? As Ron pointed out, while this holds out the promise of extensibility, it would not actually offer a lot of incremental power for streams, because the ability to "extend" streams with static methods is in reality quite limited.? So at the time, this seemed mostly a "shiny feature idea in search of a real problem." There are a number of feature ideas centering on method chaining that keep coming around every few years.? My opinion is that they are mostly "weak" features, because method chaining itself is pretty weak.? In the cases where it works out, it is pretty nice, but the set of cases where it is actually a useful structuring convention for APIs is quite limited, and it is often badly overused.? (A prime example is "async" libraries; these illustrate the pitfalls of designing APIs around an idiom that only permits linear composition.? The simplicity of "filter-map-reduce" quickly gives way to becoming a programming straitjacket.) Further, the call for more features around chaining often centers around superficial aesthetics rather than expressiveness, because chaining itself runs out of expressive ability quickly enough.? So a feature whose primary goal is "moar chaining" is suspect out of the gate. On 3/28/2023 8:37 AM, Kristofer Karlsson wrote: > Hi, I am new to this list so apologies if I'm missing any guidelines to follow. > > This feature idea includes a specific solution, but I think the > feature (some way to support a more fluent code flow with methods that > are not part of the object) is more important than the solution > (Allowing a new syntax for static method calls) so even if the > solution ends up being rejected, I hope the feature itself does not > have to be. > > I have tried searching for existing JEPs that roughly matched this > feature idea but could not find anything. Before trying to create an > actual JEP, I thought it would be useful to know if this is a > reasonable idea at all. The closest thing I've been able to find is > this stackoverflow post[1] where extension methods are discussed. > However, I'm thinking about this problem in terms of enabling a more > fluent code style at the use-site without changing any semantics, and > not in terms of designing and controlling APIs (for which the existing > interface abstractions already work very well). > > [1]:https://stackoverflow.com/questions/29466427/what-was-the-design-consideration-of-not-allowing-use-site-injection-of-extensio/29494337#29494337 > > Best regards > Kristofer > > --- > > Title: Fluent Syntax for Static Method Calls > Author: Kristofer Karlsson > Created: 2023/03/27 > Type: Feature > State: Draft > Exposure: Open > Component: specification?/?language > Scope: SE > Discussion: amber dash dev at openjdk dot java dot net > Template: 1.0 > > ## Summary > > Enhance the language by allowing static method calls to be used in a fluent way. > > ## Goals > > Enhance the language and compiler to support invoking static methods > in a fluent way. This would be a purely ergonomic change to help > developers to write code that is easier to read, write and maintain > without breaking any existing code and without requiring any changes > to the bytecode or the runtime. > > Fluent in this context refers tohttps://en.wikipedia.org/wiki/Fluent_interface > > ## Motivation > > Given the JDK additions of streams and classes such as CompletableFuture, and > already existing patterns of using builders and operating on immutable types, > writing code in a fluent style has become common, and is considered best > practice in many cases. > > For streams you would do something like: > list > .stream() > .map(x -> x + 1) > .limit(10) > .collect(Collectors.toSet()) > > and with CompletableFuture you would do something like: > CompletableFuture.completedFuture("hello") > .thenApply(s -> s + " world") > .exceptionally(e -> "An error occurred") > .thenRun(this::someCallback); > > These classes and interfaces are out of our control - they are part of the JDK - > so we can not extend them to add other potentially useful methods such > as Stream.parallel(executor), Stream.toSet(), > CompletableFuture.handleCompose(func), > CompletableFuture.orTimeout(time, unit, executor). > > We can create static methods that operate on such classes and return > new instances, > but chaining such calls together becomes clunky. Here's a (somewhat contrived) > example of what it would look like today vs what it would look like with > the proposal in place: > toSet(parallel(list.stream(), myExecutor) > .map(x -> x + 1) > .limit(10)) > > compared to: > import static MyStream.parallel; > import static MyStream.toSet; > > list.stream() > .parallel(myExecutor) > .map(x -> x + 1) > .limit(10) > .toSet() > > > And for CompletableFuture: > orTimeout( > handleCompose( > flatten( > CompletableFuture.completedFuture(null) > .exceptionally(e -> new CompletableFuture<>())), > (v, e) -> ...), > 10, TimeUnit.SECONDS, myExecutor) > > compared to: > import static MyFutures.flatten; > import static MyFutures.handleCompose; > import static MyFutures.orTimeout; > > CompletableFuture.completedFuture(null) > .exceptionally(e -> new CompletableFuture<>()) > .flatten() > .handleCompose((v, e) -> ...) > .orTimeout(10, TimeUnit.SECONDS, myExecutor) > > Note: CompletableFuture.exceptionallyCompose is an example of a method that was > initially missing but later added due to being convenient, even though it could > have been implemented as a simple chain of calls: > future.thenApply(CompletableFuture::new) > .exceptionally(e -> func(e)) > .thenCompose(Function.identity()) > > # Description > > Currently static method calls look like: > ClassName.methodName(arg1, arg2, ...); > > Combined with a static static import statement they can also look like this: > import static ClassName.methodName; > methodName(arg1, arg2, ...); > > The compiler could be modified to also recognize this form: > import static ClassName.methodName; > arg1.methodName(arg2, ...); > > For simplicity, this would only be allowed for statically imported methods. > This means that you could invoke Arrays.fill() as: > import static java.util.Arrays.fill; > int[] array = ...; > array.fill(0); > > > ## Out of scope > Technically, this feature could also be allowed for primitives. This > would mean that > you could invoke Math.abs(-1L) as (-1L).abs() and Math.exp(2.0, 3.0) > as (2.0).exp(3.0). > > However, this may introduce extra complexity for the compiler to > handle, so for the purpose > of simplifying the JEP, this is out of scope. > > ## Compatibility > > All existing valid code will continue to be valid and behave exactly as before. > > ## Risks > > If new code introduces usage of this new method invocation, there could be a > subtle change to the method resolution if the argument type introduces a > method that matches the signature. Recompiling the class after the argument type > dependency has been updated would lead to a different method resolution. > > However, this is already true for a similar case: > > public class A { > public static void main(String[] args) { > (new B()).theMethod(); > } > } > > public class B { > static void theMethod() { } > } > > will show that invoking B.theMethod() resolves to invokestatic: > 8: invokestatic #4 // Method B.theMethod:()V > > If B is then changed to: > public class B { > void theMethod() { } > } > > and we recompile A, we get: > 7: invokevirtual #4 // Method B.theMethod:()V > > If we don't recompile A, we get this error instead: > > Exception in thread "main" java.lang.IncompatibleClassChangeError: > Expected static method 'void B.theMethod()' > at A.main(A.java:3) -------------- next part -------------- An HTML attachment was scrubbed... URL: From archie.cobbs at gmail.com Tue Mar 28 18:51:11 2023 From: archie.cobbs at gmail.com (Archie Cobbs) Date: Tue, 28 Mar 2023 13:51:11 -0500 Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> References: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> Message-ID: On Tue, Mar 28, 2023 at 10:48?AM Ron Pressler wrote: > As usual, the main challenge is understanding what exactly is the problem > here ? is this a specific issue with CF and Stream or something more > general ? and if there is a general problem, what exactly is it, and does > it justify a change to the language. Only after we answer that can we > consider adding a language feature. > Great point - which also makes me curious how we should define the underlying problem here. One problem is "prettier chaining" which as Brian pointed out makes for a relatively weak case. What about another problem, which is that in Java it's too hard to "wrap" something with new functionality? I.e., this is the same problem extensions try to solve. Just to be clear, suppose I invent this (using Kristofer's example): public interface BetterStream extends Stream { BetterStream parallel(Executor e) Set toSet() @Override BetterStream filter(Predicate pred) // etc. } It's not easy to wrap Streams I encounter to convert them into BetterStreams. I agree with Brian that "API designers should control their API's" so I suppose we're talking about a true "wrap", not a "monkey patch". You can do a "wrap" today but it's tedious and brittle. Could the language make it easier somehow? I'm sure this has been discussed before. Curious what's the current status of that discussion. -Archie -- Archie L. Cobbs -------------- next part -------------- An HTML attachment was scrubbed... URL: From holo3146 at gmail.com Tue Mar 28 19:09:12 2023 From: holo3146 at gmail.com (Holo The Sage Wolf) Date: Tue, 28 Mar 2023 22:09:12 +0300 Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: References: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> Message-ID: I'm not sure if this was discussed before, but this problem was big enough that other languages did introduce language features to help with it. It is important to emphasize that like Ron said, Java is *very* conservative, so "other did it" shouldn't be read as "they did it so Java should also offer a solution" but rather it should be read as a sign that this conversation has substance. Kotlin, for example, introduced a keyword for delegating a reference (although I would argue that they took the delegating ship too far, and gave it a bit too much power). But I don't necessarily think that "wrapping" is the correct description of the problem, I think that "it is hard to compose in Java" is a better description (I'm talking about composing objects, not about composing effects and all of the fun higher order talks). This broader problem I personally feel in 2 places: in wrapping objects (e.g. like creating enhanced streams) and in life-time management, e.g. there is no easy way to compose few resources (it is not impossible, but it very cumbersome, especially when you are trying to communicate with a third party library). On Tue, Mar 28, 2023, 21:52 Archie Cobbs wrote: > On Tue, Mar 28, 2023 at 10:48?AM Ron Pressler > wrote: > >> As usual, the main challenge is understanding what exactly is the problem >> here ? is this a specific issue with CF and Stream or something more >> general ? and if there is a general problem, what exactly is it, and does >> it justify a change to the language. Only after we answer that can we >> consider adding a language feature. >> > > Great point - which also makes me curious how we should define the > underlying problem here. > > One problem is "prettier chaining" which as Brian pointed out makes for a > relatively weak case. > > What about another problem, which is that in Java it's too hard to "wrap" > something with new functionality? I.e., this is the same problem extensions > try to solve. > > Just to be clear, suppose I invent this (using Kristofer's example): > > public interface BetterStream extends Stream { > BetterStream parallel(Executor e) > Set toSet() > @Override > BetterStream filter(Predicate pred) // etc. > } > > It's not easy to wrap Streams I encounter to convert them into > BetterStreams. I agree with Brian that "API designers should control their > API's" so I suppose we're talking about a true "wrap", not a "monkey > patch". You can do a "wrap" today but it's tedious and brittle. Could the > language make it easier somehow? > > I'm sure this has been discussed before. Curious what's the current status > of that discussion. > > -Archie > > -- > Archie L. Cobbs > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Tue Mar 28 19:10:00 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 28 Mar 2023 15:10:00 -0400 Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: References: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> Message-ID: <01353081-4d52-9f69-70c1-5fbaf7139d55@oracle.com> Not to pick on your example, but I'm going to pick on your example.... You give as examples two methods you'd like to add to Stream: toSet and parallel(Executor) -- and it is notable that these examples are fairly commonly cited when this topic comes up. Note that the first is entirely a cosmetic thing; we already have collect(Collectors::toSet), so all this does is save a few characters -- its just code golf. The second example -- changing how parallel execution works -- requires reinventing almost all of the implementation of Streams (which, if you've never looked at it, is a lot more complicated than you might think.)? In which case the surface expression here is the least of your problems. Now, it's easy for someone to complain "why didn't they make streams extensible" (we actually spent a lot of time exploring how this might work), but the reality is, Streams does not actually let users plug in new operations except through defined extension points like collect(), regardless of how easy or hard the language would make that.? And the tricks that would create the illusion of doing so, like extension methods, force you to give up a significant portion of the nonfunctional value of streams, because a static "extension" method can't fuse operations, can't access the parallel machinery used by the rest of streams, can't interact with short-circuiting easily, can't take advantage of in-place optimizations, etc.? So making a "static" extension look like a built-in method with chaining actually obfuscates what is going on, depriving readers of cues about the runtime behavior. Returning to your question, the problem of "wrapping streams" is one of the streams framework having a significant amount of complexity under the hood, which makes "tapping into it" hard -- and that's the real problem.? And -- and here's the kicker -- this complexity shows up in most APIs that are candidates for heavy use of chaining anyway. On 3/28/2023 2:51 PM, Archie Cobbs wrote: > On Tue, Mar 28, 2023 at 10:48?AM Ron Pressler > wrote: > > As usual, the main challenge is understanding what exactly is the > problem here ? is this a specific issue with CF and Stream or > something more general ? and if there is a general problem, what > exactly is it, and does it justify a change to the language. Only > after we answer that can we consider adding a language feature. > > > Great point - which also makes me curious how we should define the > underlying problem here. > > One problem is "prettier chaining" which as Brian pointed out makes > for a relatively weak case. > > What about another problem, which is that in Java it's too hard to > "wrap" something with new functionality? I.e., this is the same > problem extensions try to solve. > > Just to be clear, suppose I invent this (using Kristofer's example): > > public interface BetterStream extends Stream { > ??? BetterStream parallel(Executor e) > ? ? Set toSet() > @Override > ??? BetterStream filter(Predicate pred) // etc. > } > > It's not easy to wrap Streams I encounter to convert them into > BetterStreams. I agree with Brian that "API designers should control > their API's" so I suppose we're talking about a true "wrap", not a > "monkey patch". You can do a "wrap" today but it's tedious and > brittle. Could the language make it easier somehow? > > I'm sure this has been discussed before. Curious what's the current > status of that discussion. > > -Archie > > -- > Archie L. Cobbs -------------- next part -------------- An HTML attachment was scrubbed... URL: From ron.pressler at oracle.com Tue Mar 28 19:24:55 2023 From: ron.pressler at oracle.com (Ron Pressler) Date: Tue, 28 Mar 2023 19:24:55 +0000 Subject: [External] : Re: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: References: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> Message-ID: > On 28 Mar 2023, at 20:09, Holo The Sage Wolf wrote: > > > This broader problem I personally feel in 2 places: in wrapping objects (e.g. like creating enhanced streams) and in life-time management, e.g. there is no easy way to compose few resources (it is not impossible, but it very cumbersome, especially when you are trying to communicate with a third party library). > I can tell you that there?s ongoing early-stage exploration for both of these particular problems (with either no language change at all or with a much smaller change than the one that?s been brought up here), but I don?t readily see something that could cover both. Extension methods or something along that line would not significantly help with either one. ? Ron From amaembo at gmail.com Tue Mar 28 19:58:10 2023 From: amaembo at gmail.com (Tagir Valeev) Date: Tue, 28 Mar 2023 21:58:10 +0200 Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: <01353081-4d52-9f69-70c1-5fbaf7139d55@oracle.com> References: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> <01353081-4d52-9f69-70c1-5fbaf7139d55@oracle.com> Message-ID: > The second example -- changing how parallel execution works -- requires reinventing almost all of the implementation of Streams (which, if you've never looked at it, is a lot more complicated than you might think.) In which case the surface expression here is the least of your problems. Sorry for moving the discussion away but I cannot stay aside when there's Stream API on the table :-) Implementing .parallel(fjp) (fjp, not just any executor) is not that hard as it seems. The only thing you need is to create a tiny wrapper delegate over the original stream that remembers the supplied fjp, and then submit every terminal operation to that fjp and join the result. I implemented this in my StreamEx library [1], and this one is definitely not the hardest Stream API extension that I implemented. That said, as you need to wrap Stream API anyway, you can make it quite comfortable without extension methods. You need to create a bunch of factories repeating stream sources from JDK, like StreamEx.of(Collection) instead of Collection.stream(). Not so huge work either. And then you can add .toSet() :-) With best regards, Tagir Valeev. [1] https://www.javadoc.io/static/one.util/streamex/0.8.1/one.util.streamex/one/util/streamex/AbstractStreamEx.html#parallel(java.util.concurrent.ForkJoinPool) On Tue, Mar 28, 2023 at 9:11?PM Brian Goetz wrote: > Not to pick on your example, but I'm going to pick on your example.... > > You give as examples two methods you'd like to add to Stream: toSet and > parallel(Executor) -- and it is notable that these examples are fairly > commonly cited when this topic comes up. Note that the first is entirely a > cosmetic thing; we already have collect(Collectors::toSet), so all this > does is save a few characters -- its just code golf. > > The second example -- changing how parallel execution works -- requires > reinventing almost all of the implementation of Streams (which, if you've > never looked at it, is a lot more complicated than you might think.) In > which case the surface expression here is the least of your problems. > > Now, it's easy for someone to complain "why didn't they make streams > extensible" (we actually spent a lot of time exploring how this might > work), but the reality is, Streams does not actually let users plug in new > operations except through defined extension points like collect(), > regardless of how easy or hard the language would make that. And the > tricks that would create the illusion of doing so, like extension methods, > force you to give up a significant portion of the nonfunctional value of > streams, because a static "extension" method can't fuse operations, can't > access the parallel machinery used by the rest of streams, can't interact > with short-circuiting easily, can't take advantage of in-place > optimizations, etc. So making a "static" extension look like a built-in > method with chaining actually obfuscates what is going on, depriving > readers of cues about the runtime behavior. > > Returning to your question, the problem of "wrapping streams" is one of > the streams framework having a significant amount of complexity under the > hood, which makes "tapping into it" hard -- and that's the real problem. > And -- and here's the kicker -- this complexity shows up in most APIs that > are candidates for heavy use of chaining anyway. > > > > > > On 3/28/2023 2:51 PM, Archie Cobbs wrote: > > On Tue, Mar 28, 2023 at 10:48?AM Ron Pressler > wrote: > >> As usual, the main challenge is understanding what exactly is the problem >> here ? is this a specific issue with CF and Stream or something more >> general ? and if there is a general problem, what exactly is it, and does >> it justify a change to the language. Only after we answer that can we >> consider adding a language feature. >> > > Great point - which also makes me curious how we should define the > underlying problem here. > > One problem is "prettier chaining" which as Brian pointed out makes for a > relatively weak case. > > What about another problem, which is that in Java it's too hard to "wrap" > something with new functionality? I.e., this is the same problem extensions > try to solve. > > Just to be clear, suppose I invent this (using Kristofer's example): > > public interface BetterStream extends Stream { > BetterStream parallel(Executor e) > Set toSet() > @Override > BetterStream filter(Predicate pred) // etc. > } > > It's not easy to wrap Streams I encounter to convert them into > BetterStreams. I agree with Brian that "API designers should control their > API's" so I suppose we're talking about a true "wrap", not a "monkey > patch". You can do a "wrap" today but it's tedious and brittle. Could the > language make it easier somehow? > > I'm sure this has been discussed before. Curious what's the current status > of that discussion. > > -Archie > > -- > Archie L. Cobbs > > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From amaembo at gmail.com Tue Mar 28 20:15:25 2023 From: amaembo at gmail.com (Tagir Valeev) Date: Tue, 28 Mar 2023 22:15:25 +0200 Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: References: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> <01353081-4d52-9f69-70c1-5fbaf7139d55@oracle.com> Message-ID: By the way, my experience with extension methods in Kotlin is not very exciting (hopefully, my colleagues won't hate me on this). Probably it's the tooling problem, but it appears that it's too easy to call the extension method. Like imagine that you have an object myObj of type MyType and want to convert to the OtherType. You type myObj. and check the completion options and happily find that myObj.toOtherType() is suggested, which looks just like something you need. If you are not attentive enough you won't realize that toOtherType() is an extension method that was created in a completely unrelated module in a very specific context to solve a very specific problem, and this method has poor contract and is not applicable generally. Probably, it was declared as public by accident (partially because Kotlin is public-by-default) and was never intended to be used from outside. With a static method, you normally see which class the method belongs to, so such a problem doesn't happen. However, with extension methods, you just have a new entry in the imports list, which is far away from the use-site, so it's hard to notice what you are actually depending on. I made this mistake several times until I disciplined myself to check every time where the method comes from. And I saw such a thing done by other developers as well. Just recently I was working on IntelliJ IDEA project and refactored the JavaDoc inspection UI. I decided to remove a utility class (written in Kotlin and unfortunately public) that was created nearby solely to support the UI of a single inspection. However, our internal plugin compatibility tool yelled at me that there's a third-party plugin in our plugin repository that uses an extension method declared inside of that file. Of course, the plugin has nothing in common with JavaDoc inspection. I suspect that the plugin author just completed something without even checking where it comes from. Now, I need to keep this class and have a deprecation cycle in order not to break the plugin [1]. So to summarize, ease of use of extension methods may suddenly become an unpleasant maintenance burden. With best regards, Tagir Valeev. [1] https://github.com/JetBrains/intellij-community/blob/aa39823b7d3ed082888a749fe3051688be49d2fa/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavadocUIUtil.kt On Tue, Mar 28, 2023 at 9:58?PM Tagir Valeev wrote: > > The second example -- changing how parallel execution works -- > requires reinventing almost all of the implementation of Streams (which, if > you've never looked at it, is a lot more complicated than you might > think.) In which case the surface expression here is the least of your > problems. > > Sorry for moving the discussion away but I cannot stay aside when there's > Stream API on the table :-) Implementing .parallel(fjp) (fjp, not just any > executor) is not that hard as it seems. The only thing you need is to > create a tiny wrapper delegate over the original stream that remembers the > supplied fjp, and then submit every terminal operation to that fjp and join > the result. I implemented this in my StreamEx library [1], and this one is > definitely not the hardest Stream API extension that I implemented. > > That said, as you need to wrap Stream API anyway, you can make it quite > comfortable without extension methods. You need to create a bunch of > factories repeating stream sources from JDK, like StreamEx.of(Collection) > instead of Collection.stream(). Not so huge work either. And then you can > add .toSet() :-) > > With best regards, > Tagir Valeev. > > [1] > https://www.javadoc.io/static/one.util/streamex/0.8.1/one.util.streamex/one/util/streamex/AbstractStreamEx.html#parallel(java.util.concurrent.ForkJoinPool) > > On Tue, Mar 28, 2023 at 9:11?PM Brian Goetz > wrote: > >> Not to pick on your example, but I'm going to pick on your example.... >> >> You give as examples two methods you'd like to add to Stream: toSet and >> parallel(Executor) -- and it is notable that these examples are fairly >> commonly cited when this topic comes up. Note that the first is entirely a >> cosmetic thing; we already have collect(Collectors::toSet), so all this >> does is save a few characters -- its just code golf. >> >> The second example -- changing how parallel execution works -- requires >> reinventing almost all of the implementation of Streams (which, if you've >> never looked at it, is a lot more complicated than you might think.) In >> which case the surface expression here is the least of your problems. >> >> Now, it's easy for someone to complain "why didn't they make streams >> extensible" (we actually spent a lot of time exploring how this might >> work), but the reality is, Streams does not actually let users plug in new >> operations except through defined extension points like collect(), >> regardless of how easy or hard the language would make that. And the >> tricks that would create the illusion of doing so, like extension methods, >> force you to give up a significant portion of the nonfunctional value of >> streams, because a static "extension" method can't fuse operations, can't >> access the parallel machinery used by the rest of streams, can't interact >> with short-circuiting easily, can't take advantage of in-place >> optimizations, etc. So making a "static" extension look like a built-in >> method with chaining actually obfuscates what is going on, depriving >> readers of cues about the runtime behavior. >> >> Returning to your question, the problem of "wrapping streams" is one of >> the streams framework having a significant amount of complexity under the >> hood, which makes "tapping into it" hard -- and that's the real problem. >> And -- and here's the kicker -- this complexity shows up in most APIs that >> are candidates for heavy use of chaining anyway. >> >> >> >> >> >> On 3/28/2023 2:51 PM, Archie Cobbs wrote: >> >> On Tue, Mar 28, 2023 at 10:48?AM Ron Pressler >> wrote: >> >>> As usual, the main challenge is understanding what exactly is the >>> problem here ? is this a specific issue with CF and Stream or something >>> more general ? and if there is a general problem, what exactly is it, and >>> does it justify a change to the language. Only after we answer that can we >>> consider adding a language feature. >>> >> >> Great point - which also makes me curious how we should define the >> underlying problem here. >> >> One problem is "prettier chaining" which as Brian pointed out makes for a >> relatively weak case. >> >> What about another problem, which is that in Java it's too hard to "wrap" >> something with new functionality? I.e., this is the same problem extensions >> try to solve. >> >> Just to be clear, suppose I invent this (using Kristofer's example): >> >> public interface BetterStream extends Stream { >> BetterStream parallel(Executor e) >> Set toSet() >> @Override >> BetterStream filter(Predicate pred) // etc. >> } >> >> It's not easy to wrap Streams I encounter to convert them into >> BetterStreams. I agree with Brian that "API designers should control their >> API's" so I suppose we're talking about a true "wrap", not a "monkey >> patch". You can do a "wrap" today but it's tedious and brittle. Could the >> language make it easier somehow? >> >> I'm sure this has been discussed before. Curious what's the current >> status of that discussion. >> >> -Archie >> >> -- >> Archie L. Cobbs >> >> >> -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Tue Mar 28 20:47:15 2023 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 28 Mar 2023 22:47:15 +0200 (CEST) Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: References: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> <01353081-4d52-9f69-70c1-5fbaf7139d55@oracle.com> Message-ID: <891814389.22293881.1680036435826.JavaMail.zimbra@univ-eiffel.fr> > From: "Tagir Valeev" > To: "Brian Goetz" > Cc: "Archie Cobbs" , "Ron Pressler" > , "Kristofer Karlsson" , "amber-dev" > > Sent: Tuesday, March 28, 2023 10:15:25 PM > Subject: Re: Language feature to support Fluent Syntax for Static Method Calls > By the way, my experience with extension methods in Kotlin is not very exciting > (hopefully, my colleagues won't hate me on this). Probably it's the tooling > problem, but it appears that it's too easy to call the extension method. Like > imagine that you have an object myObj of type MyType and want to convert to the > OtherType. You type myObj. and check the completion options and happily find > that myObj.toOtherType() is suggested, which looks just like something you > need. If you are not attentive enough you won't realize that toOtherType() is > an extension method that was created in a completely unrelated module in a very > specific context to solve a very specific problem, and this method has poor > contract and is not applicable generally. Probably, it was declared as public > by accident (partially because Kotlin is public-by-default) and was never > intended to be used from outside. With a static method, you normally see which > class the method belongs to, so such a problem doesn't happen. However, with > extension methods, you just have a new entry in the imports list, which is far > away from the use-site, so it's hard to notice what you are actually depending > on. > I made this mistake several times until I disciplined myself to check every time > where the method comes from. And I saw such a thing done by other developers as > well. Just recently I was working on IntelliJ IDEA project and refactored the > JavaDoc inspection UI. I decided to remove a utility class (written in Kotlin > and unfortunately public) that was created nearby solely to support the UI of a > single inspection. However, our internal plugin compatibility tool yelled at me > that there's a third-party plugin in our plugin repository that uses an > extension method declared inside of that file. Of course, the plugin has > nothing in common with JavaDoc inspection. I suspect that the plugin author > just completed something without even checking where it comes from. Now, I need > to keep this class and have a deprecation cycle in order not to break the > plugin [1]. > So to summarize, ease of use of extension methods may suddenly become an > unpleasant maintenance burden. In the defense of Kotlin (or Groovy), extension methods are the only way to add a Kotlin feel to existing Java classes (think java.lang.String). If you do not have extension methods, the other solution is to rewrite all the base classes in your language, Ceylon choose that way, but it makes the interoperability with Java libraries unnecessary difficult. Extension methods are a practical solution for a language on top of Java to improve JDK classes while keeping a good interoperability with Java libraries. As a user of the language, I find extension methods less attractive because it makes the code harder to read. For the Stream API, we discussed about adding a method transform() working the same way as String.transfom() works [2], allowing a static method to be used in a chaining way, by example collection.stream() .transform(BetterStream::of) .parallel(executor) .toSet() > With best regards, > Tagir Valeev. regards, R?mi [2] https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/String.html#transform(java.util.function.Function) > [1] [ > https://github.com/JetBrains/intellij-community/blob/aa39823b7d3ed082888a749fe3051688be49d2fa/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavadocUIUtil.kt > | > https://github.com/JetBrains/intellij-community/blob/aa39823b7d3ed082888a749fe3051688be49d2fa/java/java-impl/src/com/intellij/codeInspection/javaDoc/JavadocUIUtil.kt > ] > On Tue, Mar 28, 2023 at 9:58 PM Tagir Valeev < [ mailto:amaembo at gmail.com | > amaembo at gmail.com ] > wrote: >>> The second example -- changing how parallel execution works -- requires >>> reinventing almost all of the implementation of Streams (which, if you've never >>> looked at it, is a lot more complicated than you might think.) In which case >> > the surface expression here is the least of your problems. >> Sorry for moving the discussion away but I cannot stay aside when there's Stream >> API on the table :-) Implementing .parallel(fjp) (fjp, not just any executor) >> is not that hard as it seems. The only thing you need is to create a tiny >> wrapper delegate over the original stream that remembers the supplied fjp, and >> then submit every terminal operation to that fjp and join the result. I >> implemented this in my StreamEx library [1], and this one is definitely not the >> hardest Stream API extension that I implemented. >> That said, as you need to wrap Stream API anyway, you can make it quite >> comfortable without extension methods. You need to create a bunch of factories >> repeating stream sources from JDK, like StreamEx.of(Collection) instead of >> Collection.stream(). Not so huge work either. And then you can add .toSet() :-) >> With best regards, >> Tagir Valeev. >> [1] [ >> https://www.javadoc.io/static/one.util/streamex/0.8.1/one.util.streamex/one/util/streamex/AbstractStreamEx.html#parallel(java.util.concurrent.ForkJoinPool) >> | >> https://www.javadoc.io/static/one.util/streamex/0.8.1/one.util.streamex/one/util/streamex/AbstractStreamEx.html#parallel(java.util.concurrent.ForkJoinPool) >> ] >> On Tue, Mar 28, 2023 at 9:11 PM Brian Goetz < [ mailto:brian.goetz at oracle.com | >> brian.goetz at oracle.com ] > wrote: >>> Not to pick on your example, but I'm going to pick on your example.... >>> You give as examples two methods you'd like to add to Stream: toSet and >>> parallel(Executor) -- and it is notable that these examples are fairly commonly >>> cited when this topic comes up. Note that the first is entirely a cosmetic >>> thing; we already have collect(Collectors::toSet), so all this does is save a >>> few characters -- its just code golf. >>> The second example -- changing how parallel execution works -- requires >>> reinventing almost all of the implementation of Streams (which, if you've never >>> looked at it, is a lot more complicated than you might think.) In which case >>> the surface expression here is the least of your problems. >>> Now, it's easy for someone to complain "why didn't they make streams extensible" >>> (we actually spent a lot of time exploring how this might work), but the >>> reality is, Streams does not actually let users plug in new operations except >>> through defined extension points like collect(), regardless of how easy or hard >>> the language would make that. And the tricks that would create the illusion of >>> doing so, like extension methods, force you to give up a significant portion of >>> the nonfunctional value of streams, because a static "extension" method can't >>> fuse operations, can't access the parallel machinery used by the rest of >>> streams, can't interact with short-circuiting easily, can't take advantage of >>> in-place optimizations, etc. So making a "static" extension look like a >>> built-in method with chaining actually obfuscates what is going on, depriving >>> readers of cues about the runtime behavior. >>> Returning to your question, the problem of "wrapping streams" is one of the >>> streams framework having a significant amount of complexity under the hood, >>> which makes "tapping into it" hard -- and that's the real problem. And -- and >>> here's the kicker -- this complexity shows up in most APIs that are candidates >>> for heavy use of chaining anyway. >>> On 3/28/2023 2:51 PM, Archie Cobbs wrote: >>>> On Tue, Mar 28, 2023 at 10:48 AM Ron Pressler < [ mailto:ron.pressler at oracle.com >>>> | ron.pressler at oracle.com ] > wrote: >>>>> As usual, the main challenge is understanding what exactly is the problem here ? >>>>> is this a specific issue with CF and Stream or something more general ? and if >>>>> there is a general problem, what exactly is it, and does it justify a change to >>>>> the language. Only after we answer that can we consider adding a language >>>>> feature. >>>> Great point - which also makes me curious how we should define the underlying >>>> problem here. >>>> One problem is "prettier chaining" which as Brian pointed out makes for a >>>> relatively weak case. >>>> What about another problem, which is that in Java it's too hard to "wrap" >>>> something with new functionality? I.e., this is the same problem extensions try >>>> to solve. >>>> Just to be clear, suppose I invent this (using Kristofer's example): >>>> public interface BetterStream extends Stream { >>>> BetterStream parallel(Executor e) >>>> Set toSet() >>>> @Override >>>> BetterStream filter(Predicate pred) // etc. >>>> } >>>> It's not easy to wrap Streams I encounter to convert them into BetterStreams. I >>>> agree with Brian that "API designers should control their API's" so I suppose >>>> we're talking about a true "wrap", not a "monkey patch". You can do a "wrap" >>>> today but it's tedious and brittle. Could the language make it easier somehow? >>>> I'm sure this has been discussed before. Curious what's the current status of >>>> that discussion. >>>> -Archie >>>> -- >>>> Archie L. Cobbs -------------- next part -------------- An HTML attachment was scrubbed... URL: From amejonah at gmail.com Tue Mar 28 22:21:53 2023 From: amejonah at gmail.com (Amejonah Izoo) Date: Wed, 29 Mar 2023 00:21:53 +0200 Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: References: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> <01353081-4d52-9f69-70c1-5fbaf7139d55@oracle.com> Message-ID: <1fa784a8-5fb4-e49e-0eb3-aec41719dfb8@gmail.com> On 28.03.23 22:15, Tagir Valeev wrote: > If you are not attentive enough you won't realize that toOtherType() > is an extension method that was created in a completely unrelated > module in a very specific context to solve a very specific problem, > and this method has poor contract and is not applicable generally. > Probably, it was declared as public by accident (partially because > Kotlin is public-by-default) and was never intended to be used from > outside. With a static method, you normally see which class the method > belongs to, so such a problem doesn't happen. However, with extension > methods, you just have a new entry in the imports list, which is far > away from the use-site, so it's hard to notice what you are actually > depending?on. I would like to point to https://youtrack.jetbrains.com/issue/KT-29227/Provide-package-private-visibility-modifier-or-another-scope-reducing-mechanism for this critic. The problem you mentioned has nothing to do with extension functions per se but rather with visibility/scoping of members in general. The value of these functions is to allow adding (static/final) methods based also on generic constrains like a sum() function for Iterable. In Java you would need either nest or to convert to IntStream first which is just a identity map (mapToInt(i -> i)). I recall a discussion here about a feature to restrain certain methods to specific generic types. This is one of the main advantages of extensions besides flexibility and... well.. the ability to extend foreign types with utilities in your project (you find a missing method? just add it. Also... no one has added toPersonGroup() method to List which can be quite useful in some cases). Well... if you (@Kristofer Karlsson ) really want to use Java, for some reason instead of switching to another language, because Java will never add such features anyway (and if so, it doesn't matter, we will be dead at that point), there is still the possibility to use https://github.com/manifold-systems/manifold/tree/master/manifold-deps-parent/manifold-ext From davidalayachew at gmail.com Tue Mar 28 23:45:48 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Tue, 28 Mar 2023 19:45:48 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 Message-ID: Hello Amber Dev Team, I was reading about JEP 301 and it's unfortunate withdrawal [1]. I also took a look at the linked bugs.openjdk.org link [2]. There, Maurizio Cimadamore linked to some discussions on the spec experts group of this team, explaining why they decided to withdraw efforts on this JEP for the time being. Looking at 2 of the linked mailing list threads [3] [4] (and their subsequent links), I tried my best to understand what exactly was being explained, but failed to make sense of it. Here is the best that I could make of it. It seems like the original attempt failed because trying to put generics on Enums immediately caused issues with some of thetools that use enums - specifically, EnumSet and EnumMap. It seems to have something to do with raw types not playing well with these specialized collections. Maurizio explained the why of this, but my brain struggled to understand a lot of the terminology. I tried googling, but ended up with a lot more concepts that I struggled to understand. Repeat for a few hours before I got tired out. Key phrases that I struggled to understand from the original discussion in [3] were f-bound and well formed types. Googling the definitions to these terms felt like trying to learn integrals without knowing of derivatives or limits. Moving on the next discussion in [4], it seems like the issue with raw types had been resolved by picking and choosing where we wanted to apply that logic, implying that that could be controlled by the JVM or something. Again, struggled to understand what was being said, but what I could pick up on was that they had found a solution, and just wanted to know if the cost of it/the effort needed to implement it was worth the benefit. All of this leads me to 2 questions. 1. Could someone help me understand some of the more confusing terminology so that I can understand what is being said in those discussions? 2. (And perhaps this is answered already once I understand 1) What was the reason that enums weren't designed yo be generic in the first place? Did the designers avoid it because they felt it would be a bad fit? Thank you all for your time and patience! David Alayachew [1] = https://openjdk.org/jeps/301 [2] = https://bugs.openjdk.org/browse/JDK-8170351 [3] = https://mail.openjdk.org/pipermail/amber-spec-experts/2017-May/000041.html [4] = https://mail.openjdk.org/pipermail/amber-spec-experts/2018-December/000876.html -------------- next part -------------- An HTML attachment was scrubbed... URL: From ice1000kotlin at foxmail.com Wed Mar 29 01:33:41 2023 From: ice1000kotlin at foxmail.com (=?utf-8?B?VGVzbGEgWmhhbmc=?=) Date: Tue, 28 Mar 2023 21:33:41 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 Message-ID: Hi David, I'm not an expert, but I'd like to share my understanding. F-bound is a way of having access to the subtype in the supertype. That's a trick used by Java in the Enum interface, note that it has a generic parameter. If you have an enum T, then T extends Enum From davidalayachew at gmail.com Wed Mar 29 02:30:33 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Tue, 28 Mar 2023 22:30:33 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: Hello Tesla, Thank you for your response! > F-bound is a way of having access to the > subtype in the supertype. That's a trick > used by Java in the Enum interface, note > that it has a generic parameter. If you > have an enum T, then T extends > Enum. Note that this extends relation > has some sort of recursion -- that's the > F-bound. This somewhat clears it up. Are you basically saying that the only reason why the supertype can know about the subtype is because the relationship is recursive, and thus, the recursion allows the supertype to "know" about the subtype because the subtype is just a roundabout way of referencing itself - something the supertype DOES have access to? > A well-formed type is a type that makes > sense. You can think of it as a type > expression that doesn't contain an error. > But you need to make sure you have a > sensible criterion of "no error". Ok, this makes a lot of sense. I think my confusion came from the fact that all of my google searches dug deep into the criterion itself before explaining that it's just a set of criterion that's either arbitrarily chosen or respective to the domain where "type" is defined. Thid gave me a lot of insight. I understand the first half of my question a lot better now. Thank you for your time and help! David Alayachew > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Mar 29 02:45:01 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 28 Mar 2023 22:45:01 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: > This somewhat clears it up. Are you basically saying that the only > reason why the supertype can know about the subtype is because the > relationship is recursive, and thus, the recursion allows the > supertype to "know" about the subtype because the subtype is just a > roundabout way of referencing itself - something the supertype DOES > have access to? F-bounds are weird-looking at first: ??? abstract class Enum> { ... } in part because it's not always obvious what "T" means at first. We're used to seeing type variables that represent element types (List), or result types (Supplier), but this T really means "subclass".? If it were written ??? abstract class Enum> { Subclass[] values(); } it would already be clearer.? You might first try writing it as ??? abstract class Enum { ... } but this permits extension in unexpected ways: ??? class FooEnum extends Enum { ... } You could try refining it as ??? abstract class Enum { ... } but this still permits ??? class FooEnum extends Enum { ... } What we're trying to express is that the type argument of Enum *is* the class being declared.? Which is where the f-bound comes in: ??? abstract class Enum> { Subclass[] values(); } which can only be satisfied by a declaration of the form ??? class X extends Enum { ... } The first few times you look at it, it seems weird and magic, and then at some point it seems obvious :) From davidalayachew at gmail.com Wed Mar 29 03:25:58 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Tue, 28 Mar 2023 23:25:58 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: Hello Brian, Thank you for your response! > F-bounds are weird-looking at first: > > abstract class Enum> { ... } > > in part because it's not always obvious > what "T" means at first. We're used to > seeing type variables that represent > element types (List), or result types > (Supplier), but this T really means > "subclass". If it were written > > abstract class Enum> > { Subclass[] values(); } > > it would already be clearer. This clarified the F bounds fully, thank you. Thinking of a type parameter as a sort of subclass is something I never thought of before. I see how it applies and works here though. > You might first try writing it as > > abstract class Enum { ... } > > but this permits extension in unexpected > ways: > > class FooEnum extends Enum { ... } > > You could try refining it as > > abstract class Enum { ... } > > but this still permits > > class FooEnum extends Enum { ... } > > What we're trying to express is that the > type argument of Enum *is* the class > being declared. Which is where the > f-bound comes in: > > abstract class Enum extends Enum> { Subclass[] values(); } > > which can only be satisfied by a > declaration of the form > > class X extends Enum { ... } I had had a discussion with someone long ago [1], arguing over whether or not T extends Something is meaningfully different from T extends Something. I now see the difference. It communicates what the only type permitted should be. > The first few times you look at it, it seems > weird and magic, and then at some point > it seems obvious :) Yeah, I can definitely feel the gears turning lol. Let me ask though, is there a formal name for these type of relationships? I have seen in other discussions things like L types or Q types on Valhalla. Maybe they are entirely unrelated. Moreso my question is, is there some formal name that captures this type of relationship or logic? For example, if I want to learn integrals, I would need to first understand derivatives, how they play with that E shaped summation thing, then learn antiderivatives, and then I can learn about integrals. However, if I look up Calculus, I get a nice overhead view of each of these components, allowing me to learn one after the other until I can understand. Googling calculus is all I need to be able to learn everything I need to understand integrals. Is there a similar blanket term that covers this type of logic? I understand f bounded types now, but I want to learn about other relationships like this and don't really know the parent term they fall under. Thank you for your time and help! David Alayachew [1] = https://stackoverflow.com/a/70504547 -------------- next part -------------- An HTML attachment was scrubbed... URL: From ice1000kotlin at foxmail.com Wed Mar 29 04:55:21 2023 From: ice1000kotlin at foxmail.com (=?utf-8?B?VGVzbGEgWmhhbmc=?=) Date: Wed, 29 Mar 2023 00:55:21 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 Message-ID: Hi David, My guess would be the following subjects: + (Typed) lambda calculus (prerequisite) + Basic type theory. You need to know how the type system is defined formally + Subtyping in terms of type theory, such as System Fsub Type theory is a very interesting yet extremely hard area of research, though I personally find it very enjoyable. But again, don't fully trust me, maybe other experts have better suggestions :) Regards, Tesla ---Original--- From: "David Alayachew" From holo3146 at gmail.com Wed Mar 29 06:09:57 2023 From: holo3146 at gmail.com (Holo The Sage Wolf) Date: Wed, 29 Mar 2023 09:09:57 +0300 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: This is highly depends on how theoretical you want to get. If you are only interesting in the practical results then typed lambda calculus and friends (e.g. lambda-mu-calculus, system U) as well as type system themselves (e.g. lambda-cube, Pure Type System). To truly understand everything you would probably also need to learn some Formal Logic (some would argue that Category theory is also necessary, but I from my experience this is an over estimate of how important the theoretical ideas to the practical) The more theoretical you want, the more prerequisite will be, but generally to study the theoretical side of type theory you want to know Formal Logic very well, to have a good understanding about universal algebras and algebra subject in general (Category theory, ring theory), and having at least some understanding in set theory (specifically about the foundational side) (although there are some great type theorists who definitely skipped the set theory step, I think that skipping it is not a good idea. I'm also a set theorist, so maybe I'm biased) If you want to go into the really deep end of type theory, knowing foundational alternatives like HoTT is also needed. On Wed, Mar 29, 2023, 08:03 Tesla Zhang wrote: > Hi David, > > > My guess would be the following subjects: > > + (Typed) lambda calculus (prerequisite) > + Basic type theory. You need to know how the type system is defined > formally > + Subtyping in terms of type theory, such as System Fsub > > Type theory is a very interesting yet extremely hard area of research, > though I personally find it very enjoyable. But again, don't fully trust > me, maybe other experts have better suggestions :) > > ------------------------------ > Regards, > Tesla > ---Original--- > *From:* "David Alayachew" > *Date:* Tue, Mar 28, 2023 23:26 PM > *To:* "Brian Goetz"; > *Cc:* "amber-dev";"Tesla Zhang"< > ice1000kotlin at foxmail.com>; > *Subject:* Re: 2 questions about enums and the closed/withdrawn JEP 301 > > Hello Brian, > > Thank you for your response! > > > F-bounds are weird-looking at first: > > > > abstract class Enum> { ... } > > > > in part because it's not always obvious > > what "T" means at first. We're used to > > seeing type variables that represent > > element types (List), or result types > > (Supplier), but this T really means > > "subclass". If it were written > > > > abstract class Enum> > > { Subclass[] > values(); } > > > > it would already be clearer. > > This clarified the F bounds fully, thank you. Thinking of a type parameter > as a sort of subclass is something I never thought of before. I see how it > applies and works here though. > > > You might first try writing it as > > > > abstract class Enum { ... } > > > > but this permits extension in unexpected > > ways: > > > > class FooEnum extends Enum { ... } > > > > You could try refining it as > > > > abstract class Enum { ... } > > > > but this still permits > > > > class FooEnum extends Enum { ... } > > > > What we're trying to express is that the > > type argument of Enum *is* the class > > being declared. Which is where the > > f-bound comes in: > > > > abstract class Enum > extends Enum> { Subclass[] > values(); } > > > > which can only be satisfied by a > > declaration of the form > > > > class X extends Enum { ... } > > I had had a discussion with someone long ago [1], arguing over whether or > not T extends Something is meaningfully different from T extends > Something. I now see the difference. It communicates what the only type > permitted should be. > > > The first few times you look at it, it seems > > weird and magic, and then at some point > > it seems obvious :) > > Yeah, I can definitely feel the gears turning lol. > > Let me ask though, is there a formal name for these type of relationships? > I have seen in other discussions things like L types or Q types on > Valhalla. Maybe they are entirely unrelated. Moreso my question is, is > there some formal name that captures this type of relationship or logic? > > For example, if I want to learn integrals, I would need to first > understand derivatives, how they play with that E shaped summation thing, > then learn antiderivatives, and then I can learn about integrals. However, > if I look up Calculus, I get a nice overhead view of each of these > components, allowing me to learn one after the other until I can > understand. Googling calculus is all I need to be able to learn everything > I need to understand integrals. > > Is there a similar blanket term that covers this type of logic? I > understand f bounded types now, but I want to learn about other > relationships like this and don't really know the parent term they fall > under. > > Thank you for your time and help! > David Alayachew > > [1] = https://stackoverflow.com/a/70504547 > -------------- next part -------------- An HTML attachment was scrubbed... URL: From redio.development at gmail.com Wed Mar 29 06:56:06 2023 From: redio.development at gmail.com (Red IO) Date: Wed, 29 Mar 2023 08:56:06 +0200 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: I think if we are talking about the initial question again the elephant in the room is the lack of a way to express subtypes in supertypes (F-bounds) without using "hacks" like the recursive generic. That this (mis)use of generics is confusing is demonstrated in this thread and in many others. A good solution would be to introduce a "This" type that always refers to the current class type even when inheriting. I'm by far not the first person to suggest that. But the bigger problem is that this ship has sailed for the Enum class. Even if the This type was introduced now and used in Enum removing the generic parameter would be a braking change to the Enum class braking every code that relies on the parameter to be present. The possibility to add This regardless of the Enum class should still be considered thought. Great regards RedIODev On Wed, Mar 29, 2023, 04:45 Brian Goetz wrote: > > > This somewhat clears it up. Are you basically saying that the only > > reason why the supertype can know about the subtype is because the > > relationship is recursive, and thus, the recursion allows the > > supertype to "know" about the subtype because the subtype is just a > > roundabout way of referencing itself - something the supertype DOES > > have access to? > > F-bounds are weird-looking at first: > > abstract class Enum> { ... } > > in part because it's not always obvious what "T" means at first. We're > used to seeing type variables that represent element types (List), or > result types (Supplier), but this T really means "subclass". If it > were written > > abstract class Enum> { Subclass[] > values(); } > > it would already be clearer. You might first try writing it as > > abstract class Enum { ... } > > but this permits extension in unexpected ways: > > class FooEnum extends Enum { ... } > > You could try refining it as > > abstract class Enum { ... } > > but this still permits > > class FooEnum extends Enum { ... } > > What we're trying to express is that the type argument of Enum *is* the > class being declared. Which is where the f-bound comes in: > > abstract class Enum> { Subclass[] > values(); } > > which can only be satisfied by a declaration of the form > > class X extends Enum { ... } > > The first few times you look at it, it seems weird and magic, and then > at some point it seems obvious :) > -------------- next part -------------- An HTML attachment was scrubbed... URL: From krka at spotify.com Wed Mar 29 11:43:26 2023 From: krka at spotify.com (Kristofer Karlsson) Date: Wed, 29 Mar 2023 13:43:26 +0200 Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: <1fa784a8-5fb4-e49e-0eb3-aec41719dfb8@gmail.com> References: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> <01353081-4d52-9f69-70c1-5fbaf7139d55@oracle.com> <1fa784a8-5fb4-e49e-0eb3-aec41719dfb8@gmail.com> Message-ID: Thank you all for your well thought out replies! I've taken some time to think everything through and made some mental adjustments: With regards to streams, I definitely agree that .toSet() would not be a significant improvement over .collect(Collectors.toSet()). That was a bad example. Also for controlling parallel streams, since that is typically done at the start of setting up a stream, it will be a limited problem in practice. I can't really come up with any other reasonable examples for streams, so perhaps that train of thought should be dropped. For CompletableFuture I think there are definitely some missing methods that should be added. It is somewhat surprising given that the API is in some ways very big where many methods do almost exactly the same thing. Some of that has been fixed already (thinking primarily of exceptionallyCompose), but isn't that itself somewhat of a signal that APIs can be imperfect and may sometimes need to be fixed. I imagine this will not be the last time that happens. So, I think the problem is still a problem - but do not have good insights into the size of the problem. For me it is mostly an inconvenience that leads to more verbose code, but I've seen other people at my work do the entirely wrong thing because the right thing to do was not easily available to them in the API. The impact of such mistakes comes in the form of performance issues (blocking threads in an environment that relies on asynchronous work with futures). Thinking about the solutions to the problem, there are many options that do not require any changes. Let's look at some of them, using exceptionallyCompose as an example: If this method exists (i.e. we're in Java 9+) we can do: return someRemoteCall() // step 1 .thenApply(x -> x + x) // step 2 .thenCompose(x -> otherRemoteCall(x)) // step 3 .exceptionallyCompose(e -> retryRemoteCall(e)) // step 4 .thenApply(x -> transformResponse(x)) // step 5 In Java 8 we can implement it manually roughly like this: public static CompletableFuture exceptionallyCompose( CompletableFuture future, Function func) { return future .thenApply(CompletableFuture::completedFuture) .exceptionally(func) .thenCompose(Function.identity()); } Then we have many different ways we could write the code: var f1 = someRemoteCall() // step 1 .thenApply(x -> x + x) // step 2 .thenCompose(x -> otherRemoteCall(x)) // step 3 return exceptionallyCompose(f1, e -> retryRemoteCall(e)) // step 4 .thenApply(x -> transformResponse(x)) // step 5 Pro: method ordering matches the source code ordering Con: we need to introduce a variable for this. If this was in a lambda, we would need to use blocks. Or we could write it like this: return exceptionallyCompose( // step 4 someRemoteCall() // step 1 .thenApply(x -> x + x) // step 2 .thenCompose(x -> otherRemoteCall(x)), // step 3 e -> retryRemoteCall(e)) .thenApply(x -> transformResponse(x)) // step 5 The only problem with this is that the method ordering is less intuitive in the source code. Also, the parameter for exceptionallyCompose is 4 lines away from the method itself. Another alternative is to create a wrapper interface and use it like this: return wrapAsFuture2(someRemoteCall()) // step 1 .thenApply(x -> x + x) // step 2 .thenCompose(x -> otherRemoteCall(x)) // step 3 .exceptionallyCompose(e -> retryRemoteCall(e)) // step 4 .thenApply(x -> transformResponse(x)) // step 5 .toCompletableFuture() Here the method order is preserved, but we need to create a full wrapper of the interface which is an extra bit of boilerplate code. I did not know about the (new) transform method in String - but that's also a useful pattern! If that was added to the most important classes and interfaces in the JDK, that would solve a lot of the concrete issues I think. With that, the code would look like: return someRemoteCall() // step 1 .thenApply(x -> x + x) // step 2 .thenCompose(x -> otherRemoteCall(x)) // step 3 .transform(f -> exceptionallyCompose(f, e -> retryRemoteCall(e))) // step 4 .thenApply(x -> transformResponse(x)) // step 5 which is not too bad. If we are talking about language features, I think two other approaches would be some postfix expression extension operator (previously referred to as thrush). With something like that, the code would look like: return someRemoteCall() // step 1 .thenApply(x -> x + x) // step 2 .thenCompose(x -> otherRemoteCall(x)) // step 3 OP f -> exceptionallyCompose(f, e -> retryRemoteCall(e)) // step 4 .thenApply(x -> transformResponse(x)) // step 5 Finally, the approach with alternative syntax for static method calls. If we are concerned with hiding the static method call and it looking too much like a regular call, we could do something similar to how method references work (::) - we would just need some simple way to make it look distinctly different. Example using double periods to denote imported static method call: return someRemoteCall() // step 1 .thenApply(x -> x + x) // step 2 .thenCompose(x -> otherRemoteCall(x)) // step 3 ..exceptionallyCompose(e -> retryRemoteCall(e)) // step 4 .thenApply(x -> transformResponse(x)) // step 5 Also, as is pointed out in this email thread, I agree that the fact that many other languages have tried to address this problem with some language feature, could be an indicator that it is in fact a problem worth solving. I have concluded that my original idea will not end up being implemented, but I hope that there is something simple that can be done to reduce the problem. Best regards Kristofer From ron.pressler at oracle.com Wed Mar 29 12:32:45 2023 From: ron.pressler at oracle.com (Ron Pressler) Date: Wed, 29 Mar 2023 12:32:45 +0000 Subject: Language feature to support Fluent Syntax for Static Method Calls In-Reply-To: References: <00A71EBF-03AE-42FD-8655-F7FF0E6B9704@oracle.com> <01353081-4d52-9f69-70c1-5fbaf7139d55@oracle.com> <1fa784a8-5fb4-e49e-0eb3-aec41719dfb8@gmail.com> Message-ID: <362AEC44-154C-4A3C-AF1B-48581FB81F6E@oracle.com> > On 29 Mar 2023, at 12:43, Kristofer Karlsson wrote: > > I have concluded that my original idea will not end up being > implemented, but I hope > that there is something simple that can be done to reduce the problem. > > Best regards > Kristofer But something has been done! Virtual threads address those problems with CompletableFuture ? resulting in even prettier code than you wish for ? as well other, more serious ones. Other issues raised in the discussion, with Stream and AutoCloseable, are being worked on separately. In both of these situations, the core problem is not one of syntax at all but that the design of these constructs is intrinsically not sufficiently composable. Addressing *that* will help not only aesthetics but also bigger problems caused by that limitation. ? Ron From archie.cobbs at gmail.com Wed Mar 29 13:14:45 2023 From: archie.cobbs at gmail.com (Archie Cobbs) Date: Wed, 29 Mar 2023 08:14:45 -0500 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: On Wed, Mar 29, 2023 at 1:58?AM Red IO wrote: > The possibility to add This regardless of the Enum class should still be > considered though. > Can I say "me too"?? :) I've always wanted to be able to declare an instance method whose return type is the same type as the instance. In fluent API's, it would eliminate a bunch of override methods that are only there to narrow the return type. On the enum question... When I was programming Java before there were enums, I would homebrew my own enums. When enums were invented, I thought "Great! The compiler will do all that stuff for me". I could clearly see that it was just a matter of the compiler automatically doing a transformation I was already doing manually. Today when I want a generic enum type, I go back to homebrewing my own enums just like before, with generic parameter(s) as needed. So from a practical perspective most developers probably don't understand why this shouldn't be "easy". If a developer can do it manually, the compiler should be able to do it automatically. Also, it's kind of perplexing that the show-stopping barrier here is not with the Enum type itself, but with the EnumSet/EnumMap utility classes. Regarding that problem, would it work to just add "workaround" methods like this? public static > EnumSet noneOfGeneric(Class klass) Even though klass has type Class, at compile time, you're not going to be able to specify any Class constant other than the top enum class, so no problem there, and at runtime, this method could do introspection to find the top enum class (if necessary; unlikely) and then use that. -Archie -- Archie L. Cobbs -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Wed Mar 29 14:10:30 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Wed, 29 Mar 2023 10:10:30 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: Hello RedIODev, Thank you for your response! > I think if we are talking about the initial > question again the elephant in the room > is the lack of a way to express subtypes > in supertypes (F-bounds) without using > "hacks" like the recursive generic. That > this (mis)use of generics is confusing is > demonstrated in this thread and in many > others. Just to give some context here, I am a junior java developer. Generics is something I've only recently gotten comfortable with. And most of my understanding is rooted in "if I see this specific thing between the angle brackets, I do another specific thing to satisfy the compiler." That is about as far as my concrete understanding went until yesterday. I say all that to say, I couldn't begin to have a sense of right/wrong, confusing/intuitive, or mis/use at this level of comprehension. I understand generics well enough, and I knew that Enums had that recursive self reference. I was initially surprised when seeing it, but I have emulated it to useful effect many times over. My confusion was in trying to understand the discussion. I had a semi-decent grasp of generics as a concept, but not its terminology or why specific rules were in play (and stopping JEP 301). It's like an elementary student who understands how to manipulate fractions, but never learned/remembered the exact terms like numerator/denominator is, or why divide by zero is wrong, only that it shouldn't be done. I only know how to solve the problem, but not its deeper intricacies. > A good solution would be to introduce a > "This" type that always refers to the > current class type even when inheriting. > I'm by far not the first person to suggest > that. > > But the bigger problem is that this ship > has sailed for the Enum class. Even if the > This type was introduced now and used > in Enum removing the generic parameter > would be a braking change to the Enum > class braking every code that relies on > the parameter to be present. > > The possibility to add This regardless of > the Enum class should still be considered > thought. Well, I certainly see the utility. It certainly is a lot easier to grasp from a distance. But is the only reason why we are doing this is because the existing methodology is confusing and easy to trip up on? Or does it allow us to do stuff we couldn't before? Thank you for your help and insight! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Mar 29 14:35:18 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 29 Mar 2023 10:35:18 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: IMO the best introduction to type theory is Pierce's _Types and Programming Languages_. On 3/29/2023 12:55 AM, Tesla Zhang wrote: > Hi David, > > > My guess would be the following subjects: > > + (Typed) lambda calculus (prerequisite) > + Basic type theory. You need to know how the type system is defined > formally > + Subtyping in terms of type theory, such as System Fsub > > Type theory is a very interesting yet extremely hard area of research, > though I personally find it very enjoyable. But again, don't fully > trust me, maybe other experts have better suggestions :) > > ------------------------------------------------------------------------ > Regards, > Tesla > ---Original--- > *From:* "David Alayachew" > *Date:* Tue, Mar 28, 2023 23:26 PM > *To:* "Brian Goetz"; > *Cc:* "amber-dev";"Tesla > Zhang"; > *Subject:* Re: 2 questions about enums and the closed/withdrawn JEP 301 > > Hello Brian, > > Thank you for your response! > > > F-bounds are weird-looking at first: > > > >? ? ? abstract class Enum> { ... } > > > > in part because it's not always obvious > > what "T" means at first. We're used to > > seeing type variables that represent > > element types (List), or result types > > (Supplier), but this T really means > > "subclass".? If it were written > > > >? ? ? abstract class Enum> > > { Subclass[] > values(); } > > > >?it would already be clearer. > > This clarified the F bounds fully, thank you. Thinking of a type > parameter as a sort of subclass is something I never thought of > before. I see how it applies and works here though. > > > You might first try writing it as > > > >? ? ? abstract class Enum { ... } > > > > but this permits extension in unexpected > > ways: > > > >? ? ? class FooEnum extends Enum { ... } > > > > You could try refining it as > > > >? ? ? abstract class Enum { ... } > > > > but this still permits > > > >? ? ? class FooEnum extends Enum { ... } > > > > What we're trying to express is that the > > type argument of Enum *is* the class > > being declared.? Which is where the > > f-bound comes in: > > > >? ? ? abstract class Enum >? ? ? extends Enum> { Subclass[] > values(); } > > > > which can only be satisfied by a > > declaration of the form > > > >? ? ? class X extends Enum { ... } > > I had had a discussion with someone long ago [1], arguing over whether > or not T extends Something is meaningfully different from T extends > Something. I now see the difference. It communicates what the only > type permitted should be. > > > The first few times you look at it, it seems > > weird and magic, and then at some point > > it seems obvious :) > > Yeah, I can definitely feel the gears turning lol. > > Let me ask though, is there a formal name for these type of > relationships? I have seen in other discussions things like L types or > Q types on Valhalla. Maybe they are entirely unrelated. Moreso my > question is, is there some formal name that captures this type of > relationship or logic? > > For example, if I want to learn integrals, I would need to first > understand derivatives, how they play with that E shaped summation > thing, then learn antiderivatives, and then I can learn about > integrals. However, if I look up Calculus, I get a nice overhead view > of each of these components, allowing me to learn one after the other > until I can understand. Googling calculus is all I need to be able to > learn everything I need to understand integrals. > > Is there a similar blanket term that covers this type of logic? I > understand f bounded types now, but I want to learn about other > relationships like this and don't really know the parent term they > fall under. > > Thank you for your time and help! > David Alayachew > > [1] = https://stackoverflow.com/a/70504547 -------------- next part -------------- An HTML attachment was scrubbed... URL: From maurizio.cimadamore at oracle.com Wed Mar 29 17:52:47 2023 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Wed, 29 Mar 2023 18:52:47 +0100 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: On 29/03/2023 14:14, Archie Cobbs wrote: > Regarding that problem, would it work to just add "workaround" methods > like this? > > ? ? ? ? public static > EnumSet > noneOfGeneric(Class klass) > > Even though klass has type Class, at compile time, you're > not going to be able to specify any Class constant other than the top > enum class, so no problem there, and at runtime, this method could do > introspection to find the top enum class (if necessary; unlikely) and > then use that. I don't think that would help much. To show why, let?s step back a bit, and let's ignore enums completely. Consider the following recursive generic type declaration: |class Foo> { ... } | Now let?s define a container type for our Foos: |class Box> { X get(); void set(X x) { ... } } | Given these declaration, the following types are *not* well-formed types: |Box Box> | The following is legal: |Box | But that is also a type that has significantly different characteristics from the ones above - e.g. you can?t call |Box::set| on it. So, even before getting to generic methods accepting Foos, we have a much more fundamental problem: we can't even express the type of a variable of type |Box| in the type-system (!!). What we realized was that JEP 301 rubs exactly againt this problem (just s/Foo/Enum and s/Box/EnumSet) - which means code using non-generic enums doesn?t have a clear migration path towards generified enums. Maurizio ? -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Wed Mar 29 18:02:03 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Wed, 29 Mar 2023 14:02:03 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: Hello Archie, Thank you for your response! Also, ty again for all the help on the compiler-dev issue with the .class filenames with similar case! I appreciate all the help you all gave. > Can I say "me too"?? :) I've always wanted > to be able to declare an instance method > whose return type is the same type as the > instance. In fluent API's, it would > eliminate a bunch of override methods > that are only there to narrow the return > type. Oh cool, I never thought of it this way. Yes, that would make writing utilities way easier, and fluent api's like you said. > On the enum question... > > When I was programming Java before > there were enums, I would homebrew my > own enums. > > When enums were invented, I thought > "Great! The compiler will do all that stuff > for me". I could clearly see that it was > just a matter of the compiler > automatically doing a transformation I > was already doing manually. > > Today when I want a generic enum type, I > go back to homebrewing my own enums > just like before, with generic parameter(s) > as needed. Yeah, enums feel really intuitive with respect to what originally you were doing vs what the compiler enables for you. It feels very much like records do now. That's actually part of the reason that this lack of functionality with generics on the class level hurts so much for enums. It forces me to come crashing back down to normal classes. It's like going onto local roads doing 35 when I was cruising on the highway doing 70. > So from a practical perspective most > developers probably don't understand > why this shouldn't be "easy". If a > developer can do it manually, the > compiler should be able to do it > automatically. I've received so many great answers already, but none of them have attempted to answer the second half of the question - why was this done in the first place? I hope someone will be able to answer that eventually. Maybe it was a mistake to bundle them together in a single post. Maybe the original designer of that feature has since left the development team and it is currently unknown? > Also, it's kind of perplexing that the > show-stopping barrier here is not with the > Enum type itself, but with the > EnumSet/EnumMap utility classes. So, thanks to Tesla, Brian, and everyone else, I now feel confident enough to correct this and explain what the actual intent was. Thanks again everyone! Originally, yes, the concern was about a bunch of errors that popped up on EnumSet and EnumMap when adding generics to the enum. When digging into that, they realized that f bounded types in general don't play well with any collection, which they later explained with the term heterogeneous types. All this means is that, at the time, the team couldn't see a clear way for the type system to represent a Collection of a bunch of E's, each with their own, potentially different, type parameter. To my understanding, saying I have a Collection of a bunch of E's whose type parameter is ? (unknown) is basically saying that every one of those E's has a type parameter that is the exact same type, even if it is only the parent (like java.lang.Object). This means that you would run into a very similar issue if you tried to have a Collection of any type T, where instance of that type T has its own, separate value for its type parameter. Maurizio closed the original post by saying they were exploring ways to improve the type system so that they actually COULD model this functionality. However, until that happens, or the blocker is removed, Maurizio suggested shelving until then. The second post, however, took things in a different direction. Rather than considering raw types (and the errors that came with it) to be failure states, the team took a closer look at the rules and saw that the rules and errors thrown for generics were actually lacking in a lot places, leading to false positives and negatives. Seeing that, they then thought about trying to outright change/add rules to better represent what is and is not safe to do in generics. In short, the second post offered up the idea of saying that a more precise typing rule could be added that would allow the supertype to choose not to depend on its type parameter if it is not actually going to use it. This was a very complex idea for me to wrap my head around, but thanks to these past several comments I got it now. What that idea means is that - if I have a Collection of E's, each with their own type parameter, then I may not need to know the type parameter of any of the elements of my collection, at least not at that moment. So, if I have an enum where value1 has a type parameter of String, and value2 has a type parameter of Integer, and I stick both of these values in an Collection, I know that I have no way of knowing the type parameter of the value that I passed in until I can identify which value it is (like through a switch statement for an example). As a result, I actually don't care about the type parameter until I can single this out to the individual type. AND THEREFORE, if my error is complaining that about the type parameter of my bunch of E's, this new type rule would allow me to say "I give up the ability to know what the type parameter of the value is until I hammer down which enum value this is". Because of that, I have placed a constraint on myself that prevents me from hurting myself through a bad generic method call, and therefore, I don't need that restriction in this case. Hence, the sharper type checking rules allowing us to learn when and where this situation applies so that we can lift that constraint off of the programmer. Now, this is not a clean break, it allows some bugs that previously not possible. However, with respect to the issue of trying to represent a Collection of a generic enum, it removes the biggest issues, and the other obstacles are doable. Just the big takeaway was that, thanks to the new typing rule, we can use a raw type not only as an on-ramp for old code upgrading to generics, but go further and claim that whatever E this Collection is a Collection of can all be assumed to be heterogeneous, which allows us to say, we won't use their type parameters, so we don't need to check them at this moment. Maurizio closes it off by saying that the problem is no longer about trying to make enums be generic wile playing well with EnumMap and EnumSet. That problem has been thoroughly dealt with and is no longer a blocker. The real problem is - is this really a desirable way of doing this? Implementing the typing rules takes effort, and now, these raw types open up room for bugs in generics that weren't there before. Is giving enums generics (or more properly, allowing a generic data structure the ability to have heterogeneous E) worth the new problems that will be opened up in the generics system? In short, the answer ended up being no. They felt like the amount of complexity and the new issues plus level of effort to implement this. So, JEP withdrawn forthe time being. I could be wildly wrong here, but at least it feels like it makes sense lol. > Regarding that problem, would it work to > just add "workaround" methods like this? > > public static > > EnumSet > noneOfGeneric(Class klass) > > Even though klass has type Class extends T>, at compile time, you're not > going to be able to specify any Class > constant other than the top enum class, > so no problem there, and at runtime, this > method could do introspection to find the > top enum class (if necessary; unlikely) > and then use that. I am not sure. To my understanding, this would likely fail for the same reasons why noneOf failed. It's failing because it doesn't know how to handle heterogeneous types in a generic data structure. In this case, your enum is the heterogeneous type, and the EnumSet (or any collection) is the generic data structure. Thank you for your time and help! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Wed Mar 29 18:25:06 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Wed, 29 Mar 2023 14:25:06 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 Message-ID: Hello Tesla, Brian, and Holo, Thank you all for your responses! And thank you for the list, this is very valuable. > This highly depends on how theoretical you want to get Any suggestion you have is wanted and helpful. Don't hesitate to suggest in case it might be too fringe for my taste. I will go ahead and start checking out these books and reading up on these subjects tonight. Thank you all again! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Wed Mar 29 18:32:32 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Wed, 29 Mar 2023 14:32:32 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: Hello Maurizio, Since these are your words I am paraphrasing, could you really quickly skim through and let me know if I am off the mark? Thank you for your time and help! David Alayachew On Wed, Mar 29, 2023, 2:02 PM David Alayachew wrote: > Hello Archie, > > Thank you for your response! > > Also, ty again for all the help on the compiler-dev issue with the .class > filenames with similar case! I appreciate all the help you all gave. > > > Can I say "me too"?? :) I've always wanted > > to be able to declare an instance method > > whose return type is the same type as the > > instance. In fluent API's, it would > > eliminate a bunch of override methods > > that are only there to narrow the return > > type. > > Oh cool, I never thought of it this way. Yes, that would make writing > utilities way easier, and fluent api's like you said. > > > On the enum question... > > > > When I was programming Java before > > there were enums, I would homebrew my > > own enums. > > > > When enums were invented, I thought > > "Great! The compiler will do all that stuff > > for me". I could clearly see that it was > > just a matter of the compiler > > automatically doing a transformation I > > was already doing manually. > > > > Today when I want a generic enum type, I > > go back to homebrewing my own enums > > just like before, with generic parameter(s) > > as needed. > > Yeah, enums feel really intuitive with respect to what originally you were > doing vs what the compiler enables for you. It feels very much like records > do now. That's actually part of the reason that this lack of functionality > with generics on the class level hurts so much for enums. It forces me to > come crashing back down to normal classes. It's like going onto local roads > doing 35 when I was cruising on the highway doing 70. > > > So from a practical perspective most > > developers probably don't understand > > why this shouldn't be "easy". If a > > developer can do it manually, the > > compiler should be able to do it > > automatically. > > I've received so many great answers already, but none of them have > attempted to answer the second half of the question - why was this done in > the first place? I hope someone will be able to answer that eventually. > Maybe it was a mistake to bundle them together in a single post. > > Maybe the original designer of that feature has since left the development > team and it is currently unknown? > > > Also, it's kind of perplexing that the > > show-stopping barrier here is not with the > > Enum type itself, but with the > > EnumSet/EnumMap utility classes. > > So, thanks to Tesla, Brian, and everyone else, I now feel confident enough > to correct this and explain what the actual intent was. Thanks again > everyone! > > Originally, yes, the concern was about a bunch of errors that popped up on > EnumSet and EnumMap when adding generics to the enum. When digging into > that, they realized that f bounded types in general don't play well with > any collection, which they later explained with the term heterogeneous > types. All this means is that, at the time, the team couldn't see a clear > way for the type system to represent a Collection of a bunch of E's, each > with their own, potentially different, type parameter. To my understanding, > saying I have a Collection of a bunch of E's whose type parameter is ? > (unknown) is basically saying that every one of those E's has a type > parameter that is the exact same type, even if it is only the parent (like > java.lang.Object). > > This means that you would run into a very similar issue if you tried to > have a Collection of any type T, where instance of that type T has its own, > separate value for its type parameter. > > Maurizio closed the original post by saying they were exploring ways to > improve the type system so that they actually COULD model this > functionality. However, until that happens, or the blocker is removed, > Maurizio suggested shelving until then. > > The second post, however, took things in a different direction. Rather > than considering raw types (and the errors that came with it) to be failure > states, the team took a closer look at the rules and saw that the rules and > errors thrown for generics were actually lacking in a lot places, leading > to false positives and negatives. Seeing that, they then thought about > trying to outright change/add rules to better represent what is and is not > safe to do in generics. > > In short, the second post offered up the idea of saying that a more > precise typing rule could be added that would allow the supertype to choose > not to depend on its type parameter if it is not actually going to use it. > > This was a very complex idea for me to wrap my head around, but thanks to > these past several comments I got it now. > > What that idea means is that - if I have a Collection of E's, each with > their own type parameter, then I may not need to know the type parameter of > any of the elements of my collection, at least not at that moment. So, if I > have an enum where value1 has a type parameter of String, and value2 has a > type parameter of Integer, and I stick both of these values in an > Collection, I know that I have no way of knowing the type parameter of the > value that I passed in until I can identify which value it is (like through > a switch statement for an example). As a result, I actually don't care > about the type parameter until I can single this out to the individual > type. AND THEREFORE, if my error is complaining that about the type > parameter of my bunch of E's, this new type rule would allow me to say "I > give up the ability to know what the type parameter of the value is until I > hammer down which enum value this is". Because of that, I have placed a > constraint on myself that prevents me from hurting myself through a bad > generic method call, and therefore, I don't need that restriction in this > case. Hence, the sharper type checking rules allowing us to learn when and > where this situation applies so that we can lift that constraint off of the > programmer. > > Now, this is not a clean break, it allows some bugs that previously not > possible. However, with respect to the issue of trying to represent a > Collection of a generic enum, it removes the biggest issues, and the other > obstacles are doable. Just the big takeaway was that, thanks to the new > typing rule, we can use a raw type not only as an on-ramp for old code > upgrading to generics, but go further and claim that whatever E this > Collection is a Collection of can all be assumed to be heterogeneous, which > allows us to say, we won't use their type parameters, so we don't need to > check them at this moment. > > Maurizio closes it off by saying that the problem is no longer about > trying to make enums be generic wile playing well with EnumMap and EnumSet. > That problem has been thoroughly dealt with and is no longer a blocker. > > The real problem is - is this really a desirable way of doing this? > Implementing the typing rules takes effort, and now, these raw types open > up room for bugs in generics that weren't there before. Is giving enums > generics (or more properly, allowing a generic data structure the ability > to have heterogeneous E) worth the new problems that will be opened up in > the generics system? > > In short, the answer ended up being no. They felt like the amount of > complexity and the new issues plus level of effort to implement this. So, > JEP withdrawn forthe time being. > > I could be wildly wrong here, but at least it feels like it makes sense > lol. > > > Regarding that problem, would it work to > > just add "workaround" methods like this? > > > > public static > > > EnumSet > > noneOfGeneric(Class klass) > > > > Even though klass has type Class > extends T>, at compile time, you're not > > going to be able to specify any Class > > constant other than the top enum class, > > so no problem there, and at runtime, this > > method could do introspection to find the > > top enum class (if necessary; unlikely) > > and then use that. > > I am not sure. To my understanding, this would likely fail for the same > reasons why noneOf failed. It's failing because it doesn't know how to > handle heterogeneous types in a generic data structure. In this case, your > enum is the heterogeneous type, and the EnumSet (or any collection) is the > generic data structure. > > Thank you for your time and help! > David Alayachew > -------------- next part -------------- An HTML attachment was scrubbed... URL: From archie.cobbs at gmail.com Wed Mar 29 18:33:48 2023 From: archie.cobbs at gmail.com (Archie Cobbs) Date: Wed, 29 Mar 2023 13:33:48 -0500 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: On Wed, Mar 29, 2023 at 12:53?PM Maurizio Cimadamore < maurizio.cimadamore at oracle.com> wrote: > The following is legal: > > Box > > But that is also a type that has significantly different characteristics > from the ones above - e.g. you can?t call Box::set on it. So, even before > getting to generic methods accepting Foos, we have a much more fundamental > problem: we can't even express the type of a variable of type Box in > the type-system (!!). > Thanks Maurizio... and of course if I had actually tried what I was suggesting I would have seen the problem... ! You *can* come close though... you just have to pay for your offense with @SuppressWarnings("unchecked"): import java.util.*; public class GenericEnum { public static class Enum> { } public static class Foo extends Enum> { } public abstract static class EnumSet> implements Set { public static > EnumSet noneOfGeneric(Class c) { return null; } } @SuppressWarnings("unchecked") // this is still necessary, too bad public static void main(String[] args) { Set> fooSet = EnumSet.noneOfGeneric(Foo.class); } } Personally, I would still be happy to have generic enums even without any accomodation by EnumSet/EnumMap. Then all I would have to homebrew would be a few wrapper methods around the EnumSet/EnumMap factory methods :) -Archie -- Archie L. Cobbs -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Wed Mar 29 18:43:12 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Wed, 29 Mar 2023 14:43:12 -0400 Subject: Why don't enums allow generics since Java 5? (was "Re: 2 questions about enums and the closed/withdrawn JEP 301") Message-ID: Hello Maurizio, I hope you don't mind me asking, but no one else has answered my 2nd question. And since you were the one who wrote up most of these links that I am referencing, it's a decent shot to see if you know the answer lol. Why were enums never given generics in the first place? I know they came out in the same release, so theoretically, that was the best time possible to bake this feature in and ensure it played well from the beginning. Why didn't they? Thank you for your help and patience! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Wed Mar 29 18:45:32 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Wed, 29 Mar 2023 14:45:32 -0400 Subject: Why don't enums allow generics since Java 5? (was "Re: 2 questions about enums and the closed/withdrawn JEP 301") In-Reply-To: References: Message-ID: Whoops, this was not meant to be a new thread. On Wed, Mar 29, 2023, 2:43 PM David Alayachew wrote: > Hello Maurizio, > > I hope you don't mind me asking, but no one else has answered my 2nd > question. And since you were the one who wrote up most of these links that > I am referencing, it's a decent shot to see if you know the answer lol. > > Why were enums never given generics in the first place? I know they came > out in the same release, so theoretically, that was the best time possible > to bake this feature in and ensure it played well from the beginning. Why > didn't they? > > Thank you for your help and patience! > David Alayachew > -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Wed Mar 29 18:51:02 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 29 Mar 2023 14:51:02 -0400 Subject: Why don't enums allow generics since Java 5? (was "Re: 2 questions about enums and the closed/withdrawn JEP 301") In-Reply-To: References: Message-ID: <9539ac72-0320-d5d1-c73c-dc295cb36743@oracle.com> My assumption is (a) that they were being co-developed and so they were focused on the compilation substrate they could count on, and (b) it's just impractically difficult to think ten steps ahead. Enums as they were done in Java 5 were already many steps ahead of enums in other languages of the day; enums in C were just aliases for ints, nothing more, whereas Java's enums were full blown objects with state and could implement interfaces, with constant-specific behavior.? When you're this many steps ahead of where the rest of the world is, it's hard to see more steps ahead; it's only after using them for a decade that the limitations start to chafe. Similarly, the issues Maurizio raised about weaknesses in the generic type system were surely known at the time, and were probably dismissed as "glass .1% empty" (and reasonably so, at the time, given the scope and ambitiousness of the generics effort.)? Again, only decades later does our perspective on "no one really needs that" start to shift. > Why were enums never given generics in the first place? I know they > came out in the same release, so theoretically, that was the best time > possible to bake this feature in and ensure it played well from the > beginning. Why didn't they? From davidalayachew at gmail.com Wed Mar 29 19:08:21 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Wed, 29 Mar 2023 15:08:21 -0400 Subject: Why don't enums allow generics since Java 5? (was "Re: 2 questions about enums and the closed/withdrawn JEP 301") In-Reply-To: <9539ac72-0320-d5d1-c73c-dc295cb36743@oracle.com> References: <9539ac72-0320-d5d1-c73c-dc295cb36743@oracle.com> Message-ID: Hello Brian, Thank you for the response! > My assumption is (a) that they were > being co-developed and so they were > focused on the compilation substrate > they could count on, and (b) it's just > impractically difficult to think ten steps > ahead. Makes a lot of sense, ty. > Enums as they were done in Java 5 were > already many steps ahead of enums in > other languages of the day; enums in C > were just aliases for ints, nothing more, > whereas Java's enums were full blown > objects with state and could implement > interfaces, with constant-specific > behavior. When you're this many steps > ahead of where the rest of the world is, > it's hard to see more steps ahead; it's only > after using them for a decade that the > limitations start to chafe. Amen. Enums are my all-time favorite feature in Java for exactly these reasons, plus the lightning performance of EnumSet and EnumMap (using a bit set instead of hashing to model set inclusion was a stroke of genius imo). It was actually java enums that made me not only become a dedicated Java programmer, but it made this language my favorite over them all, even today. Even now, the only languages that I know of that have better enums (not abstract data types, enums, as in enumerated VALUES) are languages that are also on the JVM. In which case, I consider that further praise for Java's step forward on enums, since they are building off of it. Yes, it was a gigantic step above the rest of the world at the time, so it makes sense how it might not have been prioritized. > Similarly, the issues Maurizio raised > about weaknesses in the generic type > system were surely known at the time, > and were probably dismissed as "glass > .1% empty" (and reasonably so, at the > time, given the scope and ambitiousness > of the generics effort.) Again, only > decades later does our perspective on > "no one really needs that" start to shift. This also makes a lot of sense. That's a hard tradeoff to balance, not to mention the amount of crystal ball searching you have to do to imagine what the future needs might be, not just now. Thank you for the help and insight! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From ice1000kotlin at foxmail.com Wed Mar 29 20:44:19 2023 From: ice1000kotlin at foxmail.com (=?utf-8?B?VGVzbGEgWmhhbmc=?=) Date: Wed, 29 Mar 2023 16:44:19 -0400 Subject: 2 questions about enums and the closed/withdrawn JEP 301 Message-ID: Hi,  I am going to argue against formal logic, universal algebra, ring theory, category theory, and HoTT. I am doing so because I was told similarly before and I was scared away from learning type theory. It's great to learn things in general, but the prerequisites are not _that_ large. I do not think you need to know formal logic well to learn type theory. You only need the notations. Modern presentations only use a very simple subset of it to express the typing derivation, say, you only need to know what does turnstile mean, what is Gamma, and what does the big horizontal line mean. That's literally it. Universal algebra provides a categorical interpretation of algebraic data types with equations, but I would argue that they're going too far. In Java (and similarly weak type systems like Haskell) we're only talking about pattern matching, you don't have any equations. I'm also confused about ring theory, because it seems unrelated to type theory.  Category theory in this context usually provides a framework of working with semantics and I'd argue that it's not required to learn basic type theory. The category theory used to study type theory is also very advanced, so the intro level things won't help. The same applies to HoTT, it's just too far from programming and type theory itself. Unless you're interested in (infinite, 1)-topoi and their internal languages, I would argue that these shouldn't be required. Regards, Tesla ---Original--- From: "Holo The Sage Wolf" From maurizio.cimadamore at oracle.com Wed Mar 29 21:07:20 2023 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Wed, 29 Mar 2023 22:07:20 +0100 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: <9d514402-6477-536e-4e0b-47b73b6a6e44@oracle.com> On 29/03/2023 19:33, Archie Cobbs wrote: > ? ? ? ? Set> fooSet = EnumSet.noneOfGeneric(Foo.class); Of course you can say Set> - but you can't say EnumSet>. > Personally, I would still be happy to have generic enums even without > any accomodation by EnumSet/EnumMap. I hear you - but I believe that EnumSet/Map with their methods are just a reminder of what would actually happen should we unleash generic enums in the large. There are many API enum-friendly points sedimented over the years which use the exact same patterns as EnumSet/Map - all these API points will not be usable when working with a generic enum. So, even if the JDK might come up with some solution for its own problematic classes (and that solution is not even 100% satisfying), there are classes outside our control that will just not work with the new enums. One rule we try to stick to when adding new features to the Java language, is that it should look like they have been there from the start. WIth generic enums that's sadly not the case, and worse, they create a split between API points that work with them and those which don't, which is ultimately bad for the ecosystem. (That's not to say that generic enums are not useful - I still find many useful applications for them...) Maurizio -------------- next part -------------- An HTML attachment was scrubbed... URL: From holo3146 at gmail.com Wed Mar 29 21:13:59 2023 From: holo3146 at gmail.com (Holo The Sage Wolf) Date: Thu, 30 Mar 2023 00:13:59 +0300 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: References: Message-ID: I'll admit I have a high bias towards research mathematics, so maybe unintentionally I gave an advice more fitting to people who want to work in theoretical mathematics. Large portion of my message came from conversations I had with Kevin Buzzard, so it is indeed far from applications in actual programming languages (?PoC languages, logic languages, and the rest of the PV gang). One thing I'll stand by is the study of formal logic, apart from the fact I believe it is generally helpful to learn it does appear in everyday situations, especially for someone who is interested in the inner workings of a type theory. For example, to understand how to derive identities purely from the method signature (see [1]). I'm not saying that you should learn how Type theory embeds with logic, but understanding FOL is definitely someone one should at least study when they enter type theory. [1] "Theorems For Free!" By F. Wadler https://dl.acm.org/doi/pdf/10.1145/99370.99404 (The link is directly to the pdf, so if you prefer not to download from a mail, quick search for "Theorems For Free!" Will find it) On Wed, Mar 29, 2023, 23:50 Tesla Zhang wrote: > Hi, > > I am going to argue against formal logic, universal algebra, ring theory, > category theory, and HoTT. I am doing so because I was told similarly > before and I was scared away from learning type theory. It's great to learn > things in general, but the prerequisites are not _that_ large. > > I do not think you need to know formal logic well to learn type theory. > You only need the notations. Modern presentations only use a very simple > subset of it to express the typing derivation, say, you only need to know > what does turnstile mean, what is Gamma, and what does the big horizontal > line mean. That's literally it. > > Universal algebra provides a categorical interpretation of algebraic data > types with equations, but I would argue that they're going too far. In Java > (and similarly weak type systems like Haskell) we're only talking about > pattern matching, you don't have any equations. I'm also confused about > ring theory, because it seems unrelated to type theory. > > Category theory in this context usually provides a framework of working > with semantics and I'd argue that it's not required to learn basic type > theory. The category theory used to study type theory is also very > advanced, so the intro level things won't help. The same applies to HoTT, > it's just too far from programming and type theory itself. Unless you're > interested in (infinite, 1)-topoi and their internal languages, I would > argue that these shouldn't be required. > > ------------------------------ > Regards, > Tesla > ---Original--- > *From:* "Holo The Sage Wolf" > *Date:* Wed, Mar 29, 2023 02:10 AM > *To:* "Tesla Zhang"; > *Cc:* "amber-dev"; > *Subject:* Re: 2 questions about enums and the closed/withdrawn JEP 301 > > This is highly depends on how theoretical you want to get. > > If you are only interesting in the practical results then typed lambda > calculus and friends (e.g. lambda-mu-calculus, system U) as well as type > system themselves (e.g. lambda-cube, Pure Type System). > > To truly understand everything you would probably also need to learn some > Formal Logic (some would argue that Category theory is also necessary, but > I from my experience this is an over estimate of how important the > theoretical ideas to the practical) > > The more theoretical you want, the more prerequisite will be, but > generally to study the theoretical side of type theory you want to know > Formal Logic very well, to have a good understanding about universal > algebras and algebra subject in general (Category theory, ring theory), and > having at least some understanding in set theory (specifically about the > foundational side) (although there are some great type theorists who > definitely skipped the set theory step, I think that skipping it is not a > good idea. I'm also a set theorist, so maybe I'm biased) > > If you want to go into the really deep end of type theory, knowing > foundational alternatives like HoTT is also needed. > > On Wed, Mar 29, 2023, 08:03 Tesla Zhang wrote: > >> Hi David, >> >> >> My guess would be the following subjects: >> >> + (Typed) lambda calculus (prerequisite) >> + Basic type theory. You need to know how the type system is defined >> formally >> + Subtyping in terms of type theory, such as System Fsub >> >> Type theory is a very interesting yet extremely hard area of research, >> though I personally find it very enjoyable. But again, don't fully trust >> me, maybe other experts have better suggestions :) >> >> ------------------------------ >> Regards, >> Tesla >> ---Original--- >> *From:* "David Alayachew" >> *Date:* Tue, Mar 28, 2023 23:26 PM >> *To:* "Brian Goetz"; >> *Cc:* "amber-dev";"Tesla Zhang"< >> ice1000kotlin at foxmail.com>; >> *Subject:* Re: 2 questions about enums and the closed/withdrawn JEP 301 >> >> Hello Brian, >> >> Thank you for your response! >> >> > F-bounds are weird-looking at first: >> > >> > abstract class Enum> { ... } >> > >> > in part because it's not always obvious >> > what "T" means at first. We're used to >> > seeing type variables that represent >> > element types (List), or result types >> > (Supplier), but this T really means >> > "subclass". If it were written >> > >> > abstract class Enum> >> > { Subclass[] >> values(); } >> > >> > it would already be clearer. >> >> This clarified the F bounds fully, thank you. Thinking of a type >> parameter as a sort of subclass is something I never thought of before. I >> see how it applies and works here though. >> >> > You might first try writing it as >> > >> > abstract class Enum { ... } >> > >> > but this permits extension in unexpected >> > ways: >> > >> > class FooEnum extends Enum { ... } >> > >> > You could try refining it as >> > >> > abstract class Enum { ... } >> > >> > but this still permits >> > >> > class FooEnum extends Enum { ... } >> > >> > What we're trying to express is that the >> > type argument of Enum *is* the class >> > being declared. Which is where the >> > f-bound comes in: >> > >> > abstract class Enum> > extends Enum> { Subclass[] >> values(); } >> > >> > which can only be satisfied by a >> > declaration of the form >> > >> > class X extends Enum { ... } >> >> I had had a discussion with someone long ago [1], arguing over whether or >> not T extends Something is meaningfully different from T extends >> Something. I now see the difference. It communicates what the only type >> permitted should be. >> >> > The first few times you look at it, it seems >> > weird and magic, and then at some point >> > it seems obvious :) >> >> Yeah, I can definitely feel the gears turning lol. >> >> Let me ask though, is there a formal name for these type of >> relationships? I have seen in other discussions things like L types or Q >> types on Valhalla. Maybe they are entirely unrelated. Moreso my question >> is, is there some formal name that captures this type of relationship or >> logic? >> >> For example, if I want to learn integrals, I would need to first >> understand derivatives, how they play with that E shaped summation thing, >> then learn antiderivatives, and then I can learn about integrals. However, >> if I look up Calculus, I get a nice overhead view of each of these >> components, allowing me to learn one after the other until I can >> understand. Googling calculus is all I need to be able to learn everything >> I need to understand integrals. >> >> Is there a similar blanket term that covers this type of logic? I >> understand f bounded types now, but I want to learn about other >> relationships like this and don't really know the parent term they fall >> under. >> >> Thank you for your time and help! >> David Alayachew >> >> [1] = https://stackoverflow.com/a/70504547 >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: From archie.cobbs at gmail.com Wed Mar 29 22:55:59 2023 From: archie.cobbs at gmail.com (Archie Cobbs) Date: Wed, 29 Mar 2023 17:55:59 -0500 Subject: 2 questions about enums and the closed/withdrawn JEP 301 In-Reply-To: <9d514402-6477-536e-4e0b-47b73b6a6e44@oracle.com> References: <9d514402-6477-536e-4e0b-47b73b6a6e44@oracle.com> Message-ID: On Wed, Mar 29, 2023 at 4:07?PM Maurizio Cimadamore < maurizio.cimadamore at oracle.com> wrote: > > Personally, I would still be happy to have generic enums even without any > accomodation by EnumSet/EnumMap. > > I hear you - but I believe that EnumSet/Map with their methods are just a > reminder of what would actually happen should we unleash generic enums in > the large. There are many API enum-friendly points sedimented over the > years which use the exact same patterns as EnumSet/Map - all these API > points will not be usable when working with a generic enum. So, even if the > JDK might come up with some solution for its own problematic classes (and > that solution is not even 100% satisfying), there are classes outside our > control that will just not work with the new enums. One rule we try to > stick to when adding new features to the Java language, is that it should > look like they have been there from the start. WIth generic enums that's > sadly not the case, and worse, they create a split between API points that > work with them and those which don't, which is ultimately bad for the > ecosystem. > I see your point (thanks for explaining). It is a conundrum. -- Archie L. Cobbs -------------- next part -------------- An HTML attachment was scrubbed... URL: From maurizio.cimadamore at oracle.com Thu Mar 30 01:18:10 2023 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Thu, 30 Mar 2023 02:18:10 +0100 Subject: Why don't enums allow generics since Java 5? (was "Re: 2 questions about enums and the closed/withdrawn JEP 301") In-Reply-To: References: Message-ID: <22da96dc-0558-52a1-f76e-02b87c2500dc@oracle.com> Hi David, sadly I don?t know the answer to your question as I wasn?t around when enums were added to the language ;-) I agree with all Brian said. I?d also add that the failure modes we discovered when we worked on generic enums are rather obscure, as they are a result of /three/ factors: (i) you need to have some class Foo that is transitioning from being non-generic to being generic and (ii) you have to have some existing utterances of Foo nested inside some other parameterized type (e.g. |Box|) and (iii) the declaration of that second generic class has to be sufficiently strict (e.g. |Box>|). If /all/ these conditions are met, then you end up with cases where utterances of |Box|were valid before generification of Foo, but invalid afterwards. Something like this was (and still is!) pretty hard to spot. One might argue (given what we know now) that it would have been perhaps better not to make the Enum class itself generic. But the static type checking you get on stuff like |EnumSet.noneOf(Foo.class)| is hard to pass up: if |Enum| wasn?t a generic class, then there?d be no way to tie the type of the class literal to the type of the returned EnumSet. So I think the Enum generic declaration we ended up with is the result of a forced move (type-system wise). Maurizio On 29/03/2023 19:43, David Alayachew wrote: > Hello Maurizio, > > I hope you don't mind me asking, but no one else has answered my 2nd > question. And since you were the one who wrote up most of these links > that I am referencing, it's a decent shot to see if you know the > answer lol. > > Why were enums never given generics in the first place? I know they > came out in the same release, so theoretically, that was the best time > possible to bake this feature in and ensure it played well from the > beginning. Why didn't they? > > Thank you for your help and patience! > David Alayachew ? -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Thu Mar 30 02:39:31 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Wed, 29 Mar 2023 22:39:31 -0400 Subject: Why don't enums allow generics since Java 5? (was "Re: 2 questions about enums and the closed/withdrawn JEP 301") In-Reply-To: <22da96dc-0558-52a1-f76e-02b87c2500dc@oracle.com> References: <22da96dc-0558-52a1-f76e-02b87c2500dc@oracle.com> Message-ID: Hello Maurizio, Thank you for your response! > sadly I don?t know the answer to your question > as I wasn?t around when enums were added to the > language ;-) Lol, fair enough. I actually poked around the enum class itself [1] and saw that it was originally made by at least 2 folks - Josh Bloch and Neal Gafter. Maybe they read these mailing lists on occasion? > I agree with all Brian said. I?d also add that > the failure modes we discovered when we worked > on generic enums are rather obscure, as they > are a result of *three* factors: (i) you need to > have some class Foo that is transitioning from > being non-generic to being generic and (ii) you > have to have some existing utterances of Foo > nested inside some other parameterized type > (e.g. Box) and (iii) the declaration of > that second generic class has to be > sufficiently strict (e.g. Box>). > If *all* these conditions are met, then you end > up with cases where utterances of Box > were valid before generification of Foo, but > invalid afterwards. Something like this was > (and still is!) pretty hard to spot. Thank you so much for the context. This really clarifies a lot and helps me to understand even more the hesitation to push forward. Not only is it going to break code, but the scope at which it will is difficult to measure, so we can't accurately judge the cost to the benefit. > One might argue (given what we know now) that > it would have been perhaps better not to make > the Enum class itself generic. But the static > type checking you get on stuff like > EnumSet.noneOf(Foo.class) is hard to pass up: if > Enum wasn?t a generic class, then there?d be no > way to tie the type of the class literal to the > type of the returned EnumSet. So I think the > Enum generic declaration we ended up with is the > result of a forced move (type-system wise). One thing being on this mailing list has taught me is that it is all about tradeoffs when trying to figure out whether or not to put stuff into the language, let alone how. I definitely see what you mean now. Thank you for the insight and context! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Thu Mar 30 02:40:45 2023 From: davidalayachew at gmail.com (David Alayachew) Date: Wed, 29 Mar 2023 22:40:45 -0400 Subject: Why don't enums allow generics since Java 5? (was "Re: 2 questions about enums and the closed/withdrawn JEP 301") In-Reply-To: References: <22da96dc-0558-52a1-f76e-02b87c2500dc@oracle.com> Message-ID: Whoops, forgot to add the link. Here it is. [1] = https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Enum.java#L65 On Wed, Mar 29, 2023 at 10:39?PM David Alayachew wrote: > Hello Maurizio, > > Thank you for your response! > > > sadly I don?t know the answer to your question > > as I wasn?t around when enums were added to the > > language ;-) > > Lol, fair enough. I actually poked around the enum class itself [1] and > saw that it was originally made by at least 2 folks - Josh Bloch and Neal > Gafter. Maybe they read these mailing lists on occasion? > > > I agree with all Brian said. I?d also add that > > the failure modes we discovered when we worked > > on generic enums are rather obscure, as they > > are a result of *three* factors: (i) you need to > > have some class Foo that is transitioning from > > being non-generic to being generic and (ii) you > > have to have some existing utterances of Foo > > nested inside some other parameterized type > > (e.g. Box) and (iii) the declaration of > > that second generic class has to be > > sufficiently strict (e.g. Box>). > > If *all* these conditions are met, then you end > > up with cases where utterances of Box > > were valid before generification of Foo, but > > invalid afterwards. Something like this was > > (and still is!) pretty hard to spot. > > Thank you so much for the context. This really clarifies a lot and helps > me to understand even more the hesitation to push forward. Not only is it > going to break code, but the scope at which it will is difficult to > measure, so we can't accurately judge the cost to the benefit. > > > One might argue (given what we know now) that > > it would have been perhaps better not to make > > the Enum class itself generic. But the static > > type checking you get on stuff like > > EnumSet.noneOf(Foo.class) is hard to pass up: if > > Enum wasn?t a generic class, then there?d be no > > way to tie the type of the class literal to the > > type of the returned EnumSet. So I think the > > Enum generic declaration we ended up with is the > > result of a forced move (type-system wise). > > One thing being on this mailing list has taught me is that it is all about > tradeoffs when trying to figure out whether or not to put stuff into the > language, let alone how. I definitely see what you mean now. > > Thank you for the insight and context! > David Alayachew > -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidwrogers2 at gmail.com Tue Mar 28 00:56:54 2023 From: davidwrogers2 at gmail.com (DWR) Date: Mon, 27 Mar 2023 19:56:54 -0500 Subject: JEP430 Message-ID: Hi, I was wondering if there is a way to constrain the type of the embedded expressions? E.g. "for DSL Foo the embedded expressions should all be of some type of a sealed hierarchy of Node types". From reinier at projectlombok.org Thu Mar 30 23:42:13 2023 From: reinier at projectlombok.org (Reinier Zwitserloot) Date: Thu, 30 Mar 2023 18:42:13 -0500 Subject: Feedback: String Templates (JEP 430) Message-ID: This design is _awesome_ and exactly the kind of '... but we actually thought through how such features would be used and how extensive the scope could be' that makes the past ~5 years of new java features such a breath of fresh air. In particular, the notes on how IDEs are strongly encouraged to render string templates quite differently from string literals further highlights this. As a language feature, IDEs are __irrelevant__, javac isn't an IDE and the spec has no business dictating IDE builders what to do. However, in practice of course, 99% of all java code is written in an IDE so keeping that in mind is precisely the kind of thing we love to see in language design proposals. We assume folks are aware that the specification of the string template construct could have been made much simpler by simply redefining that a string template consists of __zero__ or more embedded expressions. Javac would be just fine with this simplified model, but we _want_ IDE makers to copy this BNF - they need to differentiate between 'has 1 or more embedded expressions' and 'has no embedded expressions, i.e., is a literal' even if only for allowing for different colour coding. So, please keep that part. As one might expect on a mailing list for feedback on language design proposals, most of this post will involve complaints, errors, and other whining. The 2 paragraphs above should hopefully highlight how these are mere nits on an otherwise excellent proposal. ## The JSON example There are a few problems with it. As a very minor nit, the text block with the sample JSON ends in a semi colon which is, as per JSON.org, not legal JSON. Simply remove the semicolon. Java habits, we guess. A more pernicious problem with this example is that it uses interpolate(). __This is security-wise completely broken__ and means unsafe injection is waiting to happen. It's _precisely_ the problem that the proposal spent multiple paragraphs laying out immediately prior to this example, so that really made me physically wince as we was reading through it. Possibly the aim of the example is simply to show how this works, but at the very least the example should end in: // NB: Please DO NOT USE INTERPOLATE THIS WAY - this is entirely unsafe and will cause your box to be p0wned. Which is.. kinda awkward to have to put in a spec. Perhaps just leave the example syntax as is, then leave the implementation of the StringTemplate.Processor as an exercise to the reader instead of trying to shoehorn an dubious (insecure) call to interpolate() in. Or, possibly, the interpolate() call wasn't an intentional sacrifice for the sake of example and Jim really did believe this was fine, in which case, we think the interpolate() method just has to go. The whole point of having the construct be a `StringTemplate`, with `j.l.String` instances going out of their way _NOT_ to be an implementation of that interface, is to avoid precisely this problem. I'm not sure that's a good idea; examples such as `INTER` do want interpolate, but, then again, they can just call `STR.process(st)` instead of `st.interpolate()` if they want that. Given that `interpolate()` is a bazooka programmed to blow your own feet off, perhaps it's a better idea to force anybody who wants to fire it to figure out that they can call `STR.process` instead of having `interpolate()` entice you into its dangerous waters by appearing in auto-complete dialogs on string templates. Our suggestion is to __remove__ `interpolate()` entirely for all the reasons spelled out in the "String interpolation is dangerous" section of Jim's proposal. ## Outdated statements about the nature of StringTemplate.Processor.of The text goes out of its way to say that ST.Processor.of can only produce String-returning, non-checked-exception throwing processors. But the current state of the javadoc as hosted on jlaskey's cr.openjdk.org site disagrees with this; given that the current (as per the javadoc) signature of the `of` method is: static StringTemplate.Processor of(Function process) which means we can make a checked-exception throwing, non-String returning processor just fine. This would work: ``` Processor p = StringTemplate.Processor.of(st -> { .... - something that validates and throws JVEx .... return jsonObject; }; ``` ... but while we're here, the entire of method seems pointless. After all, Processor is, itself, a functional interface, so we can just delete the `StringTemplate.Processor.of()` part of the above snippet and it would exactly the same way! We suggest the `of` method is removed. Jim's proposal makes some vague overtures about how the existence of this method lets you dance around javac's inference engine somehow but I'm not quite sure how that works, and if indeed there is a point to it, then why isn't there an `of` method in `java.util.function.Function` itself? What's the key difference between StringTemplate.Processor and any other functional interface that means an `of` method is warranted? Presumably it's because of this: ``` var PROC = StringTemplate.Processor.of(st -> new Foo()); // instead of StringTemplate.Processor PROC = st -> new Foo(); ``` However, we see some rather serious issues with this. It's trying to piecemeal fix a fundamental problem with java-the-language itself. Ordinarily, I'd say `Processor PROC = st -> new Foo();` isn't so unwieldy that it requires making this 'of' method. However, because Processor has used the 'trick' of including an ``, the signature is now unwieldy. But, this means the java core libraries are now schizophrenic: With absolutely no rational basis, _some_ of its libraries _include_ the `` part, _and_ have an `of` method, and other parts (such as _all_ of `java.util.function`!) do not. This feels like a debate that should be held at a (much) higher level, up to and including a debate that all of `java.util.function` is deprecated and replaced with a carbon copy that adds `` to all relevant signatures. Which is, understandably, a rather ridiculous proposal, but _given_ that the flagship repository of functional interfaces (`java.util.function`) _does not_ do it, either: * StringTemplate.Processor also shouldn't do it, or * java-the-language needs to solve this problem itself, so that the `java.util.function` package is no longer at design odds with the `` concept.. at which point StringTemplate.Processor definitely shouldn't have it explicitly either. Both lead to the same conclusion, which is, StringTemplate.Processor shouldn't have the ``, and with it, the `of()` method. Unless that third option (deprecate `j.u.f` and make a new package with throwable-typevars) is on the table. We get that segueing this debate into this fundamental issue is ridiculous scope creep; that's not our point. The point is: We don't think individual features/libraries should decide to piecemeal 'solve' the original sin by introducing the `` and then _also_ putting out the fires _that_ causes by tossing in an `of` method that seems, on initial glance, as completely useless. As a frequent answerer of java questions on stack overflow, the fundamental issue of checked exceptions in lambdas definitely is a topic that requires a serious conversation. But it's not fair to burden this feature proposal with it. Be that as it may, in our opinion, the `` type var shouldn't be part of this proposal, and if that goes, the `of()` method is then no longer needed either. ## Can processors have methods? We have some more pointed comments about the SQL examples, but, it also raises an interesting use case that we think just works as is, though the proposal doesn't explicitly spell it out: Can StringTemplate.Processor instances also just be used to invoke methods on? For example, if one has a 'database' object that can stringtemplate-process SQL into ResultSet instances, it's _still_ generally more convenient to have an explicit API for UPSERTs. This: ``` DB.insert("tableName") .mergeOn("uniqueColumn1", foo) .mergeOn("uniqueColumn2", bar) .put("col3", baz) .exec(); ``` is 'nicer', we think, vs: ``` DB."""INSERT INTO tableName (uniqueColumn1, uniqueColumn2, col3) VALUES (\{foo}, \{bar}, \{baz}) ON CONFLICT (foo, bar) DO UPDATE SET col3 = EXCLUDED.col3"""; ``` And before you disagree on style, note that UPSERT is a classic 'syntax really differs between DB engines' - exactly the kind of thing you might want to abstract away into a uniform API such as the first snippet. Hence, we want to have some variable `DB` such that we can write: ``` ResultSet a = DB."SELECT * FROM foo"; DB.insert("tableName")....; ``` which, looking at the FormatProcessor, just 'works' right? If somehow it doesn't work like that, we would strongly suggest the above should be possible. ## Please explain that caching thing There is a bit of a throwaway line in the proposal: > Moreover, since the text block template is constant, a more advanced template processor could compile the template into a JSONObject with placeholder values, cache that result, and then at each evaluation inject the field values into a fresh deep copy of that cached JSONObject. There would be no intermediate String anywhere. But.. how does that work? One major nit we have for this proposal is that it spends multiple _pages_ on re-explaining obvious conclusions but skips over the interesting bits, such as how this caching idea would work. So, let's say we do write this `JSON` template processor. How _DO_ we cache this? Should we shove the result of `template.fragments()` (i.e. the `List` returned by that method) in a `WeakHashMap` as key for this cache I'm supposed to write? That's a bit.. non-obvious, no? The design correctly keeps the string fragments entirely separate from the values, but we couldn't quite figure out how the language proposal intends for me to cache on this. Getting back to the SQL example, there's a more fundamental itch to the stated example. The example turns: ``` DB."SELECT * from foo WHERE name = \{name}" ``` into a `java.sql.ResultSet` object. This is.. contrary to the spirit of JDBC, which acknowledges that the step of turning a string literal into a `PreparedStatement` is on its own such a (potentially!) expensive step, that the PS is, itself, a __resource__ that needs to be separately closed (unless you rely on the mechanism where closing a Connection closes all resources that you spawned from it). In that light, imagine this code: ``` var dates = new ArrayList(); for (Person p : persons) { try (var rs = DB."SELECT birthdate FROM person WHERE id = \{p.id}") { dates.add(rs.getObject(1, LocalDate.class)); } } ``` We don't see how, but the best possible result here would be if this is internally desugared into something like: ``` ArrayList dates; try (PreparedStatement ps = con.prepareStatement("SELECT birthdate FROM person WHERE id = ?")) { dates = new ArrayList(); for (Person p : persons) { ps.setInt(1, p.id); try (var rs = ps.query()) { dates.add(rs.getObject(1, LocalDate.class)); } } } ``` ... but we don't get how a StringTemplate.Processor could possibly make this work. and, unfortunately, if it is _not possible_ to make this work, then __the example is just horrible__. In the sense that if someone actually wrote a library that worked the way Jim's proposal suggests, we would need to get out our soapbox and tell everybody how this library is a bad idea nobody should be using. Surely it can't be so incredibly difficult to find examples for how string templates are awesome that we have to 'stoop' to _fundamentally_ incompatible-with-the-notion examples! If it is not possible to adapt template processors to have this kind of powers, we're proposing that the entire 'look, this will save you from SQL injection!' example is either ditched entirely or rewritten, possibly by showing off how it would work with one of the many JDBC-abstracting libraries out there like JOOQ, JDBI, or jdbc-template. Or that the entire section gets a big fat warning caveat that none of this is actually a good idea for real use. ## Compile time checking It's on our todo list to look into it, but Brian Goetz has repeatedly mentioned that e.g. `LocalDate.of(2023, 4, 1)` is eventually going to be a compile time constant. we have no idea how this works, but, separate from whatever proposal is in the works for that, it would _awesome_ if at compile time the string template processor gets to inform the compiler about what's going on. It would be _fantastic_ if this: ``` Pattern p = REGEX."foo+(bar)"; Pattern q = REGEX."foo+(bar"; ``` results in a _compile time_ error telling me that the first line is fine, but the second line is not because in the regex, the parens aren't matched. It's probably beyond scope, but whatever Brian was cooking up for pluggable compile time constants e.g. to make `LocalDate.of` be constant, that it interops with this feature proposal. -- Reinier Zwitserloot and Roel Spilker -------------- next part -------------- An HTML attachment was scrubbed... URL: From redio.development at gmail.com Fri Mar 31 06:22:07 2023 From: redio.development at gmail.com (Red IO) Date: Fri, 31 Mar 2023 08:22:07 +0200 Subject: Feedback: String Templates (JEP 430) In-Reply-To: References: Message-ID: I definitely agree with you in many places. But I think the easiest solution to the Database example would be to simply return a prepared statement instead of a result set directly. (of course that's still an error in the proposal text) It's like directly returning the DTO from a json string template. It's 1 step to far. How I see string templates being useful is as an universal and potentially compile time string parsing system and the bad Database example shows one of its misuse potentials namingly doing more than bringing the string in a parsed and checked state. Imagine what would happen if the string template would be executed at compile time. The prepared statement would be fine but executing the query at compile time is meaningless. So a rule of thumb should be that string templates should be treated as if executed at compile time ignoring rather they actually are or not. I see the compiletimeness of stringtemplates as something optional. It should definitely be part of the documentation that it might be executed at compile time (in the future) since treating it as something compile time helps to avoid mistakes like the one explained above. Great regards RedIODev On Fri, Mar 31, 2023, 03:44 Reinier Zwitserloot wrote: > This design is _awesome_ and exactly the kind of '... but we actually > thought through how such features would be used and how extensive the scope > could be' that makes the past ~5 years of new java features such a breath > of fresh air. > > > In particular, the notes on how IDEs are strongly encouraged to render > string templates quite differently from string literals further highlights > this. As a language feature, IDEs are __irrelevant__, javac isn't an IDE > and the spec has no business dictating IDE builders what to do. However, in > practice of course, 99% of all java code is written in an IDE so keeping > that in mind is precisely the kind of thing we love to see in language > design proposals. We assume folks are aware that the specification of the > string template construct could have been made much simpler by simply > redefining that a string template consists of __zero__ or more embedded > expressions. Javac would be just fine with this simplified model, but we > _want_ IDE makers to copy this BNF - they need to differentiate between > 'has 1 or more embedded expressions' and 'has no embedded expressions, > i.e., is a literal' even if only for allowing for different colour coding. > So, please keep that part. > > > As one might expect on a mailing list for feedback on language design > proposals, most of this post will involve complaints, errors, and other > whining. The 2 paragraphs above should hopefully highlight how these are > mere nits on an otherwise excellent proposal. > > > ## The JSON example > > > There are a few problems with it. As a very minor nit, the text block with > the sample JSON ends in a semi colon which is, as per JSON.org, not legal > JSON. Simply remove the semicolon. Java habits, we guess. > > > A more pernicious problem with this example is that it uses interpolate(). > __This is security-wise completely broken__ and means unsafe injection is > waiting to happen. It's _precisely_ the problem that the proposal spent > multiple paragraphs laying out immediately prior to this example, so that > really made me physically wince as we was reading through it. Possibly the > aim of the example is simply to show how this works, but at the very least > the example should end in: > > > // NB: Please DO NOT USE INTERPOLATE THIS WAY - this is entirely unsafe > and will cause your box to be p0wned. > > > Which is.. kinda awkward to have to put in a spec. Perhaps just leave the > example syntax as is, then leave the implementation of the > StringTemplate.Processor as an exercise to the reader instead of trying to > shoehorn an dubious (insecure) call to interpolate() in. > > > Or, possibly, the interpolate() call wasn't an intentional sacrifice for > the sake of example and Jim really did believe this was fine, in which > case, we think the interpolate() method just has to go. The whole point of > having the construct be a `StringTemplate`, with `j.l.String` instances > going out of their way _NOT_ to be an implementation of that interface, is > to avoid precisely this problem. I'm not sure that's a good idea; examples > such as `INTER` do want interpolate, but, then again, they can just call > `STR.process(st)` instead of `st.interpolate()` if they want that. Given > that `interpolate()` is a bazooka programmed to blow your own feet off, > perhaps it's a better idea to force anybody who wants to fire it to figure > out that they can call `STR.process` instead of having `interpolate()` > entice you into its dangerous waters by appearing in auto-complete dialogs > on string templates. Our suggestion is to __remove__ `interpolate()` > entirely for all the reasons spelled out in the "String interpolation is > dangerous" section of Jim's proposal. > > > ## Outdated statements about the nature of StringTemplate.Processor.of > > > The text goes out of its way to say that ST.Processor.of can only produce > String-returning, non-checked-exception throwing processors. > > > But the current state of the javadoc as hosted on jlaskey's cr.openjdk.org > site disagrees with this; given that the current (as per the javadoc) > signature of the `of` method is: > > > static StringTemplate.Processor > of(Function process) > > > which means we can make a checked-exception throwing, non-String returning > processor just fine. This would work: > > > ``` > > Processor p = > StringTemplate.Processor.of(st -> { > > .... - something that validates and throws JVEx .... > > return jsonObject; > > }; > > ``` > > > ... but while we're here, the entire of method seems pointless. After all, > Processor is, itself, a functional interface, so we can just delete the > `StringTemplate.Processor.of()` part of the above snippet and it would > exactly the same way! > > > We suggest the `of` method is removed. Jim's proposal makes some vague > overtures about how the existence of this method lets you dance around > javac's inference engine somehow but I'm not quite sure how that works, and > if indeed there is a point to it, then why isn't there an `of` method in > `java.util.function.Function` itself? What's the key difference between > StringTemplate.Processor and any other functional interface that means an > `of` method is warranted? Presumably it's because of this: > > > ``` > > var PROC = StringTemplate.Processor.of(st -> new Foo()); > > // instead of > > StringTemplate.Processor PROC = st -> new Foo(); > > ``` > > > However, we see some rather serious issues with this. It's trying to > piecemeal fix a fundamental problem with java-the-language itself. > > > Ordinarily, I'd say `Processor PROC = st -> new Foo();` isn't so > unwieldy that it requires making this 'of' method. However, because > Processor has used the 'trick' of including an ``, the > signature is now unwieldy. > > > But, this means the java core libraries are now schizophrenic: With > absolutely no rational basis, _some_ of its libraries _include_ the ` extends Throwable>` part, _and_ have an `of` method, and other parts (such > as _all_ of `java.util.function`!) do not. > > > This feels like a debate that should be held at a (much) higher level, up > to and including a debate that all of `java.util.function` is deprecated > and replaced with a carbon copy that adds `` to all > relevant signatures. Which is, understandably, a rather ridiculous > proposal, but _given_ that the flagship repository of functional interfaces > (`java.util.function`) _does not_ do it, either: > > > * StringTemplate.Processor also shouldn't do it, or > > * java-the-language needs to solve this problem itself, so that the > `java.util.function` package is no longer at design odds with the ` extends Throwable>` concept.. at which point StringTemplate.Processor > definitely shouldn't have it explicitly either. > > > Both lead to the same conclusion, which is, StringTemplate.Processor > shouldn't have the ``, and with it, the `of()` method. > Unless that third option (deprecate `j.u.f` and make a new package with > throwable-typevars) is on the table. > > > We get that segueing this debate into this fundamental issue is ridiculous > scope creep; that's not our point. The point is: We don't think individual > features/libraries should decide to piecemeal 'solve' the original sin by > introducing the `` and then _also_ putting out the > fires _that_ causes by tossing in an `of` method that seems, on initial > glance, as completely useless. > > > As a frequent answerer of java questions on stack overflow, the > fundamental issue of checked exceptions in lambdas definitely is a topic > that requires a serious conversation. But it's not fair to burden this > feature proposal with it. > > > Be that as it may, in our opinion, the `` type var > shouldn't be part of this proposal, and if that goes, the `of()` method is > then no longer needed either. > > > ## Can processors have methods? > > > We have some more pointed comments about the SQL examples, but, it also > raises an interesting use case that we think just works as is, though the > proposal doesn't explicitly spell it out: > > > Can StringTemplate.Processor instances also just be used to invoke methods > on? For example, if one has a 'database' object that can > stringtemplate-process SQL into ResultSet instances, it's _still_ generally > more convenient to have an explicit API for UPSERTs. This: > > > ``` > > DB.insert("tableName") > > .mergeOn("uniqueColumn1", foo) > > .mergeOn("uniqueColumn2", bar) > > .put("col3", baz) > > .exec(); > > ``` > > > is 'nicer', we think, vs: > > > ``` > > DB."""INSERT INTO tableName (uniqueColumn1, uniqueColumn2, col3) VALUES > > (\{foo}, \{bar}, \{baz}) ON CONFLICT (foo, bar) DO > > UPDATE SET col3 = EXCLUDED.col3"""; > > ``` > > > And before you disagree on style, note that UPSERT is a classic 'syntax > really differs between DB engines' - exactly the kind of thing you might > want to abstract away into a uniform API such as the first snippet. > > > Hence, we want to have some variable `DB` such that we can write: > > > ``` > > ResultSet a = DB."SELECT * FROM foo"; > > DB.insert("tableName")....; > > ``` > > > which, looking at the FormatProcessor, just 'works' right? If somehow it > doesn't work like that, we would strongly suggest the above should be > possible. > > > ## Please explain that caching thing > > > There is a bit of a throwaway line in the proposal: > > > > Moreover, since the text block template is constant, a more advanced > template processor could compile the template into a JSONObject with > placeholder values, cache that result, and then at each evaluation inject > the field values into a fresh deep copy of that cached JSONObject. There > would be no intermediate String anywhere. > > > But.. how does that work? One major nit we have for this proposal is that > it spends multiple _pages_ on re-explaining obvious conclusions but skips > over the interesting bits, such as how this caching idea would work. > > > So, let's say we do write this `JSON` template processor. How _DO_ we > cache this? Should we shove the result of `template.fragments()` (i.e. the > `List` returned by that method) in a `WeakHashMap` as key for this > cache I'm supposed to write? > > > That's a bit.. non-obvious, no? > > > The design correctly keeps the string fragments entirely separate from the > values, but we couldn't quite figure out how the language proposal intends > for me to cache on this. > > > Getting back to the SQL example, there's a more fundamental itch to the > stated example. The example turns: > > > ``` > > DB."SELECT * from foo WHERE name = \{name}" > > ``` > > > into a `java.sql.ResultSet` object. This is.. contrary to the spirit of > JDBC, which acknowledges that the step of turning a string literal into a > `PreparedStatement` is on its own such a (potentially!) expensive step, > that the PS is, itself, a __resource__ that needs to be separately closed > (unless you rely on the mechanism where closing a Connection closes all > resources that you spawned from it). > > > In that light, imagine this code: > > > ``` > > var dates = new ArrayList(); > > for (Person p : persons) { > > try (var rs = DB."SELECT birthdate FROM person WHERE id = \{p.id}") { > > dates.add(rs.getObject(1, LocalDate.class)); > > } > > } > > ``` > > > We don't see how, but the best possible result here would be if this is > internally desugared into something like: > > > ``` > > ArrayList dates; > > try (PreparedStatement ps = con.prepareStatement("SELECT birthdate FROM > person WHERE id = ?")) { > > dates = new ArrayList(); > > for (Person p : persons) { > > ps.setInt(1, p.id); > > try (var rs = ps.query()) { > > dates.add(rs.getObject(1, LocalDate.class)); > > } > > } > > } > > ``` > > > ... but we don't get how a StringTemplate.Processor could possibly make > this work. > > > and, unfortunately, if it is _not possible_ to make this work, then __the > example is just horrible__. > > In the sense that if someone actually wrote a library that worked the way > Jim's proposal suggests, we would need to > > get out our soapbox and tell everybody how this library is a bad idea > nobody should be using. > > > Surely it can't be so incredibly difficult to find examples for how string > templates are awesome that we have to 'stoop' > > to _fundamentally_ incompatible-with-the-notion examples! > > > If it is not possible to adapt template processors to have this kind of > powers, we're proposing that the entire 'look, this will save you from SQL > injection!' example is either ditched entirely or rewritten, possibly by > showing off how it would work with one of the many JDBC-abstracting > libraries out there like JOOQ, JDBI, or jdbc-template. Or that the entire > section gets a big fat warning caveat that none of this is actually a good > idea for real use. > > > ## Compile time checking > > > It's on our todo list to look into it, but Brian Goetz has repeatedly > mentioned that e.g. `LocalDate.of(2023, 4, 1)` is eventually going to be a > compile time constant. we have no idea how this works, but, separate from > whatever proposal is in the works for that, it would _awesome_ if at > compile time the string template processor gets to inform the compiler > about what's going on. It would be _fantastic_ if this: > > > ``` > > Pattern p = REGEX."foo+(bar)"; > > Pattern q = REGEX."foo+(bar"; > > ``` > > > results in a _compile time_ error telling me that the first line is fine, > but the second line is not because in the regex, the parens aren't matched. > > > It's probably beyond scope, but whatever Brian was cooking up for > pluggable compile time constants e.g. to make `LocalDate.of` be constant, > that it interops with this feature proposal. > > > -- Reinier Zwitserloot and Roel Spilker > > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From james.laskey at oracle.com Fri Mar 31 11:53:40 2023 From: james.laskey at oracle.com (Jim Laskey) Date: Fri, 31 Mar 2023 11:53:40 +0000 Subject: JEP430 In-Reply-To: References: Message-ID: The current design allows for you to ensure that the arguments are of a certain type at runtime. Compile time checks are not possible. import java.lang.StringTemplate.Processor; public class Main { record Complex(double r, double i) { public String toString() { return STR."[\{r} + \{i}i]"; } } private static final Processor DSL = st -> { for (Object value : st.values()) { if (!(value instanceof Complex)) { throw new IllegalArgumentException("Value is not Complex"); } } // ... return StringTemplate.interpolate(st.fragments(), st.values()); }; public static void main(String... args) { Complex x = new Complex(10.0, 3.33), y = new Complex(20.0, 6.67); String result = DSL."\{x} + \{y}"; System.out.println(result); System.out.println("Done!"); } } ?????????????????????????? [10.0 + 3.33i] + [20.0 + 6.67i] Done! Cheers, ? Jim On Mar 27, 2023, at 9:56 PM, DWR wrote: Hi, I was wondering if there is a way to constrain the type of the embedded expressions? E.g. "for DSL Foo the embedded expressions should all be of some type of a sealed hierarchy of Node types". -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Fri Mar 31 12:46:47 2023 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 31 Mar 2023 14:46:47 +0200 (CEST) Subject: JEP430 In-Reply-To: References: Message-ID: <621430271.25313777.1680266807667.JavaMail.zimbra@univ-eiffel.fr> > From: "Jim Laskey" > To: "DWR" > Cc: "amber-dev" > Sent: Friday, March 31, 2023 1:53:40 PM > Subject: Re: JEP430 > The current design allows for you to ensure that the arguments are of a certain > type at runtime. Compile time checks are not possible. > import java.lang.StringTemplate.Processor; > public class Main { > record Complex(double r, double i) { > public String toString() { > return STR."[\{r} + \{i}i]"; > } > } > private static final Processor DSL = st -> { > for (Object value : st.values()) { > if (!(value instanceof Complex)) { > throw new IllegalArgumentException("Value is not Complex"); > } > } > // ... > return StringTemplate.interpolate(st.fragments(), st.values()); > }; > public static void main(String... args) { > Complex x = new Complex(10.0, 3.33), y = new Complex(20.0, 6.67); > String result = DSL."\{x} + \{y}"; > System.out.println(result); > System.out.println("Done!"); > } > } > ?????????????????????????? > [10.0 + 3.33i] + [20.0 + 6.67i] > Done! > Cheers, > ? Jim If the code has not changed too much since the last time I checked, in term of performance, you can improve the code a bit and do the runtime check only once per callsite. The StringTemplate implementation class is always the same per callsite and retain the type of the captured values. Thus you can install an inlining cache on that class so the arguments types can be checked once per callsite. This is similar to the way VarH R?mi >> On Mar 27, 2023, at 9:56 PM, DWR wrote: >> Hi, I was wondering if there is a way to constrain the type of the >> embedded expressions? E.g. "for DSL Foo the embedded expressions >> should all be of some type of a sealed hierarchy of Node types". -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Fri Mar 31 12:50:16 2023 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 31 Mar 2023 14:50:16 +0200 (CEST) Subject: JEP430 In-Reply-To: <621430271.25313777.1680266807667.JavaMail.zimbra@univ-eiffel.fr> References: <621430271.25313777.1680266807667.JavaMail.zimbra@univ-eiffel.fr> Message-ID: <1525877688.25315072.1680267016501.JavaMail.zimbra@univ-eiffel.fr> > From: "Remi Forax" > To: "Jim Laskey" > Cc: "DWR" , "amber-dev" > Sent: Friday, March 31, 2023 2:46:47 PM > Subject: Re: JEP430 >> From: "Jim Laskey" >> To: "DWR" >> Cc: "amber-dev" >> Sent: Friday, March 31, 2023 1:53:40 PM >> Subject: Re: JEP430 >> The current design allows for you to ensure that the arguments are of a certain >> type at runtime. Compile time checks are not possible. >> import java.lang.StringTemplate.Processor; >> public class Main { >> record Complex(double r, double i) { >> public String toString() { >> return STR."[\{r} + \{i}i]"; >> } >> } >> private static final Processor DSL = st -> { >> for (Object value : st.values()) { >> if (!(value instanceof Complex)) { >> throw new IllegalArgumentException("Value is not Complex"); >> } >> } >> // ... >> return StringTemplate.interpolate(st.fragments(), st.values()); >> }; >> public static void main(String... args) { >> Complex x = new Complex(10.0, 3.33), y = new Complex(20.0, 6.67); >> String result = DSL."\{x} + \{y}"; >> System.out.println(result); >> System.out.println("Done!"); >> } >> } >> ?????????????????????????? >> [10.0 + 3.33i] + [20.0 + 6.67i] >> Done! >> Cheers, >> ? Jim > If the code has not changed too much since the last time I checked, in term of > performance, you can improve the code a bit and do the runtime check only once > per callsite. > The StringTemplate implementation class is always the same per callsite and > retain the type of the captured values. > Thus you can install an inlining cache on that class so the arguments types can > be checked once per callsite. > This is similar to the way VarH With the last sentence: This is similar to the way VarHandles are checked. R?mi > R?mi >>> On Mar 27, 2023, at 9:56 PM, DWR wrote: >>> Hi, I was wondering if there is a way to constrain the type of the >>> embedded expressions? E.g. "for DSL Foo the embedded expressions >>> should all be of some type of a sealed hierarchy of Node types". -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Mar 31 15:37:00 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 31 Mar 2023 11:37:00 -0400 Subject: Feedback: String Templates (JEP 430) In-Reply-To: References: Message-ID: <66693205-3ae6-cbc8-59d9-8d2af6a42891@oracle.com> There's a lot here; let me try and excavate the actionable points. > ## The JSON example > My take-away here is "the JEP text suggests that JSON document creation is mere interpolation, and it should remind the audience that injection attacks are as big a concern in JSON as in SQL." This is worth considering. As you know, the examples surrounding JSON and JDBC, are just examples.? This JEP doesn't specify processors for these domains; that's a separate effort, some of which will probably happen in the JDK, and some of which will happen in the broader ecosystem.? The examples here are intended to motivate why the design is as it is, and illustrate the sort of use cases we have in mind. > just call `STR.process(st)` instead of `st.interpolate()` Your point here is, I think, that the `interpolate` is an "attractive nuisance", and will make it too easy for people to do the wrong thing without thinking.? A valid concern, but on the other hand, is the extra step going to deter those folks? > > ... but while we're here, the entire of method seems pointless. After > all, Processor is, itself, a functional interface, so we can just > delete the `StringTemplate.Processor.of()` part of the above snippet > and it would exactly the same way! > This whole section seems an unnecessary detour, and kind of a big distraction. > ## Can processors have methods? > > > We have some more pointed comments about the SQL examples, but, it > also raises an interesting use case that we think just works as is, > though the proposal doesn't explicitly spell it out: > > > Can StringTemplate.Processor instances also just be used to invoke > methods on? > Of course; they're objects, and ST.P is just one interface they can implement. > ## Please explain that caching thing This is a valid thing to wonder about, and we can discuss it separately, but it's not really what the JEP should be focused on. While the role of the JEP changes over the lifecycle of a feature, at this stage, where the feature is ready to be delivered, its primary role is outlining what was implemented, with some supporting "why".? Including a "Processor Writer's Guide" here would be out of scope, and interfere with the primary role of the JEP. > Getting back to the SQL example, there's a more fundamental itch to > the stated example. The example turns: > > > ``` > > DB."SELECT * from foo WHERE name = \{name}" > > ``` > > > into a `java.sql.ResultSet` object. This is.. contrary to the spirit > of JDBC, which acknowledges that the step of turning a string literal > into a `PreparedStatement` is on its own such a (potentially!) > expensive step, that the PS is, itself, a __resource__ that needs to > be separately closed (unless you rely on the mechanism where closing a > Connection closes all resources that you spawned from it). > As mentioned, the motivating examples are just that: motivating examples.?? There is ample time to discuss the API of a future proposed SQL processor for the JDK, if and when we do one. But, I'm having a hard time distilling your actual concern here.? Is it that you think the language feature is flawed in that it cannot express what you think is the right way to do it, or are you simply worried that people will take the examples as blessing of "here's a good way to do something in XYZ domain", and blindly follow it? > > ## Compile time checking > Definitely a different topic :) -------------- next part -------------- An HTML attachment was scrubbed... URL: From reinier at projectlombok.org Fri Mar 31 17:11:34 2023 From: reinier at projectlombok.org (Reinier Zwitserloot) Date: Fri, 31 Mar 2023 12:11:34 -0500 Subject: Feedback: String Templates (JEP 430) In-Reply-To: <66693205-3ae6-cbc8-59d9-8d2af6a42891@oracle.com> References: <66693205-3ae6-cbc8-59d9-8d2af6a42891@oracle.com> Message-ID: On 31 Mar 2023 at 17:37:00, Brian Goetz wrote: > The examples here are intended to motivate why the design is as it is, and > illustrate the sort of use cases we have in mind. > For the JSON example, it?s just an oversimplification in the example code, but, some sort of callout that the example code is oversimplified so much it?s now insecure is warranted, no? Or just remove the implementation code, given that it isn?t all that illustrative - the example works just fine without it, given that the call-site code is the key part of the example. Just a minor nit. > just call `STR.process(st)` instead of `st.interpolate()` > > > Your point here is, I think, that the `interpolate` is an "attractive > nuisance", and will make it too easy for people to do the wrong thing > without thinking. A valid concern, but on the other hand, is the extra > step going to deter those folks? > I think it?ll deter a significant amount. The obvious place to look for ?how do I String Template Processor? is, at least in my experience, the same place folks look for just about any other API: The methods directly available on StringTemplate. The easy action to take, once someone has decided to write a processor, is to let the IDE generate the required signature, and then auto-complete the available parameters and methods from there. By removing interpolate() from that lowest-effort flow, you *force* someone to go out and look for it, which gives them some time to think and stands at least some chance of highlighting the security implications of st.interpolate()/STR.process(st). This is always a very tough discussion: How much should the language design prevent silly mistakes. On one hand, perfection is quite impossible: The universe is far too good at coming up with incompetence, there is no way to stop all foreseeable abuse of an API, and therefore, that cannot be a goal. However, making an API where the obvious way to use it is subtly but dangerously incorrect, is, and surely this requires no debate, bad API design. The trick is figuring out where to draw the line. Especially considering that interpolate() can always be added later, but if added, cannot be removed, I recommend *not* including it in the first release of this API. The javadoc of Processor and/or StringTemplate itself should explain how one can interpolate (by using STR.process(st)), with all due warnings about the dangers of doing so. > > ... but while we're here, the entire of method seems pointless. After all, > Processor is, itself, a functional interface, so we can just delete the > `StringTemplate.Processor.of()` part of the above snippet and it would > exactly the same way! > > > This whole section seems an unnecessary detour, and kind of a big > distraction. > I felt it necessary to explain that background in order to get to the recommendation, which is to remove the and the static of() method from ST.Processor. Whilst of can always be added later, the choice to include the throwable or not looks like one that java will have to live with once released, whichever choice is made. Adding here would complicate any attempt to ?fix? the checked exceptions+lambdas issue java has in some later java release. Possibly that ship has sailed (enough APIs have added it to their functional interfaces that, if java ever adds something to help with checked-ex-in-lambda, that feature will have to contend with it). The greatest (in my opinion) feat of java 1.5?s generics is that the existing java.util. APIs all survived 100% unscathed: Somebody designing the API of j.u.ArrayList immediately after the release of generics would come up with the exact same API that ArrayList has today. That?s great, given that AL was designed well before 1.5. Generics thus weren?t just ?entirely backwards compatible? in the sense that the term ?backwards compatible? is commonly used, but it was even more backwards compatible than that: It was *culturally* backwards compatible. In that sense, records were very slightly non-culture-compatible: LocalDate.getYear() now stands out as somewhat weird: LocalDate feels like a record (and may one day be a record, certainly it?ll get a deconstructor in the same release of java that introduces that concept formally) - and ?year? feels like a property. Nevertheless, unlike records would suggest, the method to retrieve this property is called getYear() and not year(). This is a very minor nit - there were vastly more important factors to decide between getX() and x() style getters for records than this. In that sense, it is odd that string processors have checked-exception-as-a-type-var when j.u.Function interfaces don?t. Removing it from Processor is the most obvious way to tackle that incongruence. Including a "Processor Writer's Guide" here would be out of scope, and > interfere with the primary role of the JEP. > Yes, a full tutorial on how to use the feature is well beyond scope. However, explaining what the JEP does to make something possible that it claims is possible - that should be in scope, and what I?m interested in. Even a simple throwaway line such as: The List returned by fragments() is, per lexical node in the AST, constant, as in, same ref every time. The list of values obviously wouldn?t be. is all that is needed (if indeed that is how string templates are going to work). > But, I'm having a hard time distilling your actual concern here. Is it > that you think the language feature is flawed in that it cannot express > what you think is the right way to do it, or are you simply worried that > people will take the examples as blessing of "here's a good way to do > something in XYZ domain", and blindly follow it? > The proposal as written would be better if it uses examples that don?t require a bevy of caveats, but whether its worthwhile to spend the time on coming up with better ones - that?s not for me to say. Probably not - that role is better served by separate feedback. As was written at the top, pretty much all of the feedback is more or less at ?nitpick? level. String templates are great for the JSON use case, that much is clear. I?m pretty sure string templates will be a boon in some form to SQL-in-java, but it?s less clear precisely how much of a boon it will be. It's a common enough usecase, and one rife with the problems this proposal tries to tackle (keeping the intermixing of java expressions and the SQL readable without introducing SQL injection attacks), that ensuring that the proposal as written will be suitable for that particular job is important. If e.g. Lukas Eder (leads the JOOQ project) starts the work on figuring out how JOOQ could be improved with string templates, and reports back - that seems like extremely useful feedback. Trying to figure out how one would go about it in his stead, I got stuck due to the lack of details on how the caching feature works. > ## Compile time checking > > > Definitely a different topic :) > > The idea that I can type REGEX.?(foo)+bar? and get compile-time validation of the regex, and perhaps even compile-time construction of a thompson-NFA style tree so that the regex loads and runs faster at runtime (shift some of the ?work? to compile-time) is *very* exciting, though ? - I don?t see anything fundamental in the proposed design of string templates that would complicate work on compile-time constants / processing, so perhaps I shouldn?t have raised the point in the first place. ? Reinier Zwitserloot -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Mar 31 18:57:02 2023 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 31 Mar 2023 14:57:02 -0400 Subject: Feedback: String Templates (JEP 430) In-Reply-To: References: <66693205-3ae6-cbc8-59d9-8d2af6a42891@oracle.com> Message-ID: <23c6229c-3e85-9be6-4d2e-7ded42a4a798@oracle.com> >> >>> >>> ... but while we're here, the entire of method seems pointless. >>> After all, Processor is, itself, a functional interface, so we can >>> just delete the `StringTemplate.Processor.of()` part of the above >>> snippet and it would exactly the same way! >>> >> >> This whole section seems an unnecessary detour, and kind of a big >> distraction. > > I felt it necessary ... Yes, of course by "this is a big distraction", I really meant "let's spend a lot more time on it" :) > In that sense, records were very slightly non-culture-compatible: > |LocalDate.getYear()|?now stands out as somewhat weird: Yes, and this was a carefully considered decision.? In the end we decided against doubling down on the mistakes of the past, which means that some old code now looks, well, old.? That doesn't mean we're in a hurry to do this every time, but in the right situations, it is a move we can sometimes justify. > In that sense, it is odd that string processors have > checked-exception-as-a-type-var when |j.u.Function|?interfaces don?t. Sorry, I disagree with this (which is why I tried to prune this direction.)? We considered the role of exceptions in Function and friends, and we considered the role of exceptions in ST.Processor, and because each context was different, we came to different answers for different interfaces.? And that's OK!? Because functional interfaces were designed to work with or without exceptions.? This is not any sort of "glaring inconsistency", just the result of a process that requires contextual judgement being applied in different contexts. > Removing it from Processor is the most obvious way to tackle that > incongruence. IMO this is the kind of "consistency" that certain kinds of hobgoblins are attracted to :) > >>> >>> ## Compile time checking >>> >> >> Definitely a different topic :) >> > > The idea that I can type |REGEX.?(foo)+bar?|?and get compile-time > validation of the regex, and perhaps even compile-time construction of > a thompson-NFA style tree so that the regex loads and runs faster at > runtime (shift some of the ?work? to compile-time) is /very/?exciting, > though ? Yes, it is.? And I understand why it is tempting to want to backdoor some of that in through this feature, but this is not how we're going to get to a consistent and grounded form of compile-time evaluation.? So that will have to wait until we can do it right. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Fri Mar 31 19:30:31 2023 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 31 Mar 2023 21:30:31 +0200 (CEST) Subject: Feedback: String Templates (JEP 430) In-Reply-To: References: <66693205-3ae6-cbc8-59d9-8d2af6a42891@oracle.com> Message-ID: <740055836.25615197.1680291031692.JavaMail.zimbra@univ-eiffel.fr> > From: "Reinier Zwitserloot" > To: "amber-dev" > Cc: "Brian Goetz" > Sent: Friday, March 31, 2023 7:11:34 PM > Subject: Re: Feedback: String Templates (JEP 430) > On 31 Mar 2023 at 17:37:00, Brian Goetz < [ mailto:brian.goetz at oracle.com | > brian.goetz at oracle.com ] > wrote: >> The examples here are intended to motivate why the design is as it is, and >> illustrate the sort of use cases we have in mind. > For the JSON example, it?s just an oversimplification in the example code, but, > some sort of callout that the example code is oversimplified so much it?s now > insecure is warranted, no? Or just remove the implementation code, given that > it isn?t all that illustrative - the example works just fine without it, given > that the call-site code is the key part of the example. Just a minor nit. >>> just call `STR.process(st)` instead of `st.interpolate()` >> Your point here is, I think, that the `interpolate` is an "attractive nuisance", >> and will make it too easy for people to do the wrong thing without thinking. A >> valid concern, but on the other hand, is the extra step going to deter those >> folks? > I think it?ll deter a significant amount. The obvious place to look for ?how do > I String Template Processor? is, at least in my experience, the same place > folks look for just about any other API: The methods directly available on > StringTemplate . The easy action to take, once someone has decided to write a > processor, is to let the IDE generate the required signature, and then > auto-complete the available parameters and methods from there. By removing > interpolate() from that lowest-effort flow, you force someone to go out and > look for it, which gives them some time to think and stands at least some > chance of highlighting the security implications of st.interpolate() / > STR.process(st) . > This is always a very tough discussion: How much should the language design > prevent silly mistakes. On one hand, perfection is quite impossible: The > universe is far too good at coming up with incompetence, there is no way to > stop all foreseeable abuse of an API, and therefore, that cannot be a goal. > However, making an API where the obvious way to use it is subtly but > dangerously incorrect, is, and surely this requires no debate, bad API design. > The trick is figuring out where to draw the line. Especially considering that > interpolate() can always be added later, but if added, cannot be removed, I > recommend not including it in the first release of this API. The javadoc of > Processor and/or StringTemplate itself should explain how one can interpolate > (by using STR.process(st) ), with all due warnings about the dangers of doing > so. I agree that interpolate() is too easy to misuse but at the same time, it's a useful primitive. I wonder if the solution is to add an escape function, a function that takes an Object and returns an Object that should escape the values to interpolate. Something like public String StringTemplate.interpolate(UnaryOperator escapeFunction) { ... } By asking for an escape function, we are making the API safer to use. R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From john.r.rose at oracle.com Fri Mar 31 20:23:20 2023 From: john.r.rose at oracle.com (John Rose) Date: Fri, 31 Mar 2023 13:23:20 -0700 Subject: Feedback: String Templates (JEP 430) In-Reply-To: <740055836.25615197.1680291031692.JavaMail.zimbra@univ-eiffel.fr> References: <66693205-3ae6-cbc8-59d9-8d2af6a42891@oracle.com> <740055836.25615197.1680291031692.JavaMail.zimbra@univ-eiffel.fr> Message-ID: On 31 Mar 2023, at 12:30, Remi Forax wrote: > ? > I agree that interpolate() is too easy to misuse but at the same time, > it's a useful primitive. +1 > I wonder if the solution is to add an escape function, a function that > takes an Object and returns an Object that should escape the values to > interpolate. > > Something like > public String StringTemplate.interpolate(UnaryOperator > escapeFunction) { ... } > > By asking for an escape function, we are making the API safer to use. But the workaround is saying `interpolate(x->x)` and grumbling about ceremony. That workaround doesn?t get much closer to exposing the root problem. Also, the unary operator, if we were to do this functionally as suggested, needs to ?see? more context about each value that it would be interpolated. It seems to me that part of the problem here is the responsibility for escaping is on the wrong side of the fence, with `interpolate` as presently discussed. When offered a function whose contract knows only about string joining, the party producing stringy bits to join into a correct DSL statement is burdened with responsibility of figuring out how and when to escape them, and in their various DSL-specific contexts. But surely that knowledge belongs more exactly to the ST processor, not to the supplier of interpolation values. Some languages have context-dependent quoting rules. Note that Remi?s suggested unary function doesn?t see the context. It could be given context as `interpolate((x,c)->?)`, and that begs a very good question about the type of c. What I?m saying is that c is not something the supplier of interpolation values should be forced to worry about. For example in SQL values are quoted with a single quote but names are quoted with a different kind of quote, often vendor-specific; yuck. Fighting against code-injection might require getting the correct contextual flavor of quote in each case, if not for SQL then for more complex templated notations. It?s lucky for SQL that textually doubling ` ' ` to ` '' ` will cover most use cases, regardless of context, but that?s just luck. JSON has distinct kinds of values, which would require distinct tactics for validation and/or quoting; you need quote-escapes for string bodies and field names but not for numbers. If you were trying to do Java templates you?d want to know the contextual difference between char and string literals. Generally speaking, getting the quoting right is not the direct responsibility directly of people supplying values to interpolate, but rather the responsibility of the party weaving together a (correct) template (SQL or JSON or ?). Asking the value-supplier to shoulder the burden of correct quoting requires a mix of two kinds of expertise (business logic and query syntax), which is how bugs happen. I think I would prefer to see a formulation of interpolate which would require users to take apart the ST processor, lower it into a plain-string-cat template processor, and then run a natively string-cat-ing format operation on it; after that it can be lifted back to its DSL, with fingers crossed that we got avoided bad injections. But I admit I haven?t figured out the details, so that?s just a vague suggestion? What I hope is clear is my point about separating concerns, between knowing how and when to escape a value *in a particular place*, and coming up with a set of interpolation values for those places. It?s rooted in the distinction between an envelope and its contents. Quoting (and validation) is something envelope-specific. Contents are usually specific to some completely unrelated domain of business logic. Unless API users are helped to separate those concerns, there will be confusion, exploitable in attacks. ? John -------------- next part -------------- An HTML attachment was scrubbed... URL: From john.r.rose at oracle.com Fri Mar 31 20:28:00 2023 From: john.r.rose at oracle.com (John Rose) Date: Fri, 31 Mar 2023 13:28:00 -0700 Subject: Feedback: String Templates (JEP 430) In-Reply-To: References: <66693205-3ae6-cbc8-59d9-8d2af6a42891@oracle.com> <740055836.25615197.1680291031692.JavaMail.zimbra@univ-eiffel.fr> Message-ID: <4611E876-8470-44B4-B182-F226F662D482@oracle.com> P.S. As a for-instance, a good way to separate the internal concerns of envelope logic from the concerns of a payload-providing client is to make the low-level, expert-only string interpolation function be `protected` in the abstract DSL-implementor class. Then the clients only use DSL-specific API points, but internally the string assembly happens smoothly. I guess that?s not an option with interfaces, but it is one of the classic ways to avoid the confusion between envelope logic and payload logic. On 31 Mar 2023, at 13:23, John Rose wrote: > What I hope is clear is my point about separating concerns, between > knowing how and when to escape a value *in a particular place*, and > coming up with a set of interpolation values for those places. It?s > rooted in the distinction between an envelope and its contents. > Quoting (and validation) is something envelope-specific. Contents are > usually specific to some completely unrelated domain of business > logic. Unless API users are helped to separate those concerns, there > will be confusion, exploitable in attacks. > -------------- next part -------------- An HTML attachment was scrubbed... URL: