From tanksherman27 at gmail.com Sun May 1 08:40:25 2022 From: tanksherman27 at gmail.com (Julian Waters) Date: Sun, 1 May 2022 16:40:25 +0800 Subject: Classes without Object support at runtime Message-ID: Something I forgot to mention in the previous post is that fields could be private by default, as opposed to public like methods are. This was something that a private discussion reminded me about. best regards, Julian From jlahoda at openjdk.java.net Mon May 2 08:13:00 2022 From: jlahoda at openjdk.java.net (Jan Lahoda) Date: Mon, 2 May 2022 08:13:00 GMT Subject: git: openjdk/amber: patterns-record-deconstruction3: Cleanup. Message-ID: Changeset: bd504175 Author: Jan Lahoda Date: 2022-05-02 10:11:55 +0000 URL: https://git.openjdk.java.net/amber/commit/bd5041756082bd9faa2e16fa4377fd994b4b2391 Cleanup. ! src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java ! src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java From duke at openjdk.java.net Mon May 2 12:58:04 2022 From: duke at openjdk.java.net (Aggelos Biboudis) Date: Mon, 2 May 2022 12:58:04 GMT Subject: [patterns-record-deconstruction3] RFR: Fix parse error with nested record classes and empty record component list Message-ID: This PR addresses the following parse error. The following snippet of code: interface W { record X1() implements W {} } public int test(W w) { return switch (w) { case W.X1() -> 1; }; } reports that it cannot find symbol. error: cannot find symbol case W.X1() -> 1; ^ symbol: method X1() location: interface W ------------- Commit messages: - Fix parse error with nested record classes and empty record component list Changes: https://git.openjdk.java.net/amber/pull/83/files Webrev: https://webrevs.openjdk.java.net/?repo=amber&pr=83&range=00 Stats: 63 lines in 2 files changed: 58 ins; 2 del; 3 mod Patch: https://git.openjdk.java.net/amber/pull/83.diff Fetch: git fetch https://git.openjdk.java.net/amber pull/83/head:pull/83 PR: https://git.openjdk.java.net/amber/pull/83 From duke at openjdk.java.net Mon May 2 12:58:05 2022 From: duke at openjdk.java.net (Aggelos Biboudis) Date: Mon, 2 May 2022 12:58:05 GMT Subject: [patterns-record-deconstruction3] RFR: Fix parse error with nested record classes and empty record component list In-Reply-To: References: Message-ID: On Fri, 29 Apr 2022 15:54:13 GMT, Aggelos Biboudis wrote: > This PR addresses the following parse error. > > The following snippet of code: > > > interface W { > record X1() implements W {} > } > > public int test(W w) { > return switch (w) { > case W.X1() -> 1; > }; > } > > > reports that it cannot find symbol. > > > error: cannot find symbol > case W.X1() -> 1; > ^ > symbol: method X1() > location: interface W thx @lahodaj for offering the complete fix! ------------- PR: https://git.openjdk.java.net/amber/pull/83 From duke at openjdk.java.net Mon May 2 13:54:07 2022 From: duke at openjdk.java.net (duke) Date: Mon, 2 May 2022 13:54:07 GMT Subject: git: openjdk/amber: patterns-record-deconstruction3: 3 new changesets Message-ID: <4d3670dd-9afe-45ad-b807-49de26ca8893@openjdk.java.net> Changeset: 266120ab Author: Angelos Bimpoudis Date: 2022-04-27 17:36:21 +0000 URL: https://git.openjdk.java.net/amber/commit/266120ab131e0205fc5a07ff1dcce49638945a00 Fix parse error with nested record classes and empty record component list Co-authored-by: Jan Lahoda Co-authored-by: Aggelos Biboudis ! src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java + test/langtools/tools/javac/patterns/EmptyRecordClass.java Changeset: 7d1f2373 Author: Jan Lahoda <51319204+lahodaj at users.noreply.github.com> Committer: GitHub Date: 2022-05-02 15:51:57 +0000 URL: https://git.openjdk.java.net/amber/commit/7d1f23737b87b38d81286d28f83d73ed8b54beeb Update JavacParser.java ! src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java Changeset: 6e702c50 Author: Jan Lahoda <51319204+lahodaj at users.noreply.github.com> Committer: GitHub Date: 2022-05-02 15:52:22 +0000 URL: https://git.openjdk.java.net/amber/commit/6e702c50314c72a86cdb65d6d778b94189ee7c5b Merge pull request #83 from biboudis/fix-rec-pattern-in-iface-empty Fix parse error with nested record classes and empty record component list From duke at openjdk.java.net Mon May 2 13:55:39 2022 From: duke at openjdk.java.net (Aggelos Biboudis) Date: Mon, 2 May 2022 13:55:39 GMT Subject: [patterns-record-deconstruction3] RFR: Fix parse error with nested record classes and empty record component list [v2] In-Reply-To: References: Message-ID: > This PR addresses the following parse error. > > The following snippet of code: > > > interface W { > record X1() implements W {} > } > > public int test(W w) { > return switch (w) { > case W.X1() -> 1; > }; > } > > > reports that it cannot find symbol. > > > error: cannot find symbol > case W.X1() -> 1; > ^ > symbol: method X1() > location: interface W Aggelos Biboudis has updated the pull request with a new target base due to a merge or a rebase. The incremental webrev excludes the unrelated changes brought in by the merge/rebase. ------------- Changes: - all: https://git.openjdk.java.net/amber/pull/83/files - new: https://git.openjdk.java.net/amber/pull/83/files/266120ab..7d1f2373 Webrevs: - full: https://webrevs.openjdk.java.net/?repo=amber&pr=83&range=01 - incr: https://webrevs.openjdk.java.net/?repo=amber&pr=83&range=00-01 Stats: 1 line in 1 file changed: 0 ins; 0 del; 1 mod Patch: https://git.openjdk.java.net/amber/pull/83.diff Fetch: git fetch https://git.openjdk.java.net/amber pull/83/head:pull/83 PR: https://git.openjdk.java.net/amber/pull/83 From jlahoda at openjdk.java.net Mon May 2 13:55:40 2022 From: jlahoda at openjdk.java.net (Jan Lahoda) Date: Mon, 2 May 2022 13:55:40 GMT Subject: [patterns-record-deconstruction3] Withdrawn: Fix parse error with nested record classes and empty record component list In-Reply-To: References: Message-ID: On Fri, 29 Apr 2022 15:54:13 GMT, Aggelos Biboudis wrote: > This PR addresses the following parse error. > > The following snippet of code: > > > interface W { > record X1() implements W {} > } > > public int test(W w) { > return switch (w) { > case W.X1() -> 1; > }; > } > > > reports that it cannot find symbol. > > > error: cannot find symbol > case W.X1() -> 1; > ^ > symbol: method X1() > location: interface W This pull request has been closed without being integrated. ------------- PR: https://git.openjdk.java.net/amber/pull/83 From jlahoda at openjdk.java.net Tue May 3 07:40:49 2022 From: jlahoda at openjdk.java.net (Jan Lahoda) Date: Tue, 3 May 2022 07:40:49 GMT Subject: git: openjdk/amber: patterns-record-deconstruction3: Cleanup. Message-ID: <1727b708-dbb8-4ab2-9555-817c7766b0e3@openjdk.org> Changeset: 5d300fd6 Author: Jan Lahoda Date: 2022-05-02 15:57:03 +0000 URL: https://git.openjdk.java.net/amber/commit/5d300fd6041f496b955d3f9055873bcdae85d9d6 Cleanup. ! src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java From brian.goetz at oracle.com Wed May 4 19:08:10 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 4 May 2022 15:08:10 -0400 Subject: Classes without Object support at runtime In-Reply-To: References: Message-ID: <4515006b-24e6-990a-5a84-9f8dbf09b0a5@oracle.com> When it comes to evolving the programming model, it is always more helpful to discuss what problem you are trying to solve, than the shape of any particular solution.? You allude to the problem briefly ("pay heavy taxes") but I would start there.? Adding new kinds of classes adds complexity to "everybody's Java"; such complexity has to be paid for somehow.? What payoff are you imagining? On 4/30/2022 4:44 AM, Julian Waters wrote: > Hi all, > > As part of a small suggestion towards the drafts for new language features, > could we have classes that have no support from the JVM whatsoever for > creating instances of themselves? (Essentially acting as a container for > methods, much like static utility classes but with less boilerplate). > Although Java's object system is powerful, quite a sizable amount of Java > code in production doesn't require the massive amount of flexibility and > utility that objects provide, and in this instance the language bares its > ugly head and begins making developers "pay heavy taxes" (as the amber-docs > put it) to write code that isn't tightly bound to OOP, in this instance > "objectless" classes could help developers write more concise code without > going through the pain of all the extra ceremony to write code that's > stateless or doesn't need to take advantage of objects. How they're > declared and any useful features that could be tacked on to them could be > discussed later on, but right now getting their basic structure right is > probably more important. > > It'd be illegal for them to have an instance initialization > method/constructor at compile time and runtime, no other classes can extend > them, and anything declared inside them is always ACC_STATIC. As a bonus, > since this type of class wouldn't have to worry about subclasses, we could > change their access modifiers at the language level for convenience, so > that protected instead means it can only be accessed within the same > package, and have no access modifier mean "public" by default. This class > type would have none of the runtime infrastructure needed to create > instances of itself, and the new and invokespecial opcodes would > ideally not accept this type of class, though there may be a runtime cost > related to checks that would have to be put in place for this which I'm not > currently aware of > > Would appreciate feedback on the idea and any improvements that could be > made to it/any possible shortcomings > > best regards, > Julian From brian.goetz at oracle.com Wed May 4 19:40:40 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 4 May 2022 15:40:40 -0400 Subject: Contributing to the Amber docs In-Reply-To: References: <2047526974.18306549.1651215300425.JavaMail.zimbra@u-pem.fr> Message-ID: <29b729b3-c08e-5fb9-e17e-7f5a20ef7322@oracle.com> The eg-drafts folder exists as a place for attachments to the EG list, for things that can't be represented as inline text (e.g., PDF files, images.) I think one of the reasons you've had trouble getting feedback is that you are presenting *solutions* before presenting a clear explanation of the problem you are trying to solve, and then getting caught up in the surface details of the solution. First, you need to convince people there is a problem; then you need to convince them that its a problem that needs to be solved in the language; only then can you start to explore potential solutions (usually not just one) in the language. On 4/29/2022 7:24 AM, Julian Waters wrote: > Darn, that's a bummer. Thanks for the reply though, I'll see what I can do > with just the mailing list for now to get feedback on anything new (The > last one seems to be the hard part) > > best regards, > Julian > > On Fri, Apr 29, 2022 at 2:55 PM Remi Forax wrote: > >> Nope :) >> >> You have to convince people first by posting your issue/feature on the >> mailing list. >> >> The best is to first write a blog post, gather opinions on it and then >> post the result on the mailing list, because the bar to introduce a feature >> in Java is quite high so it's very important to have a very good the first >> draft. >> >> regards, >> R?mi >> >> ----- Original Message ----- >>> From: "Julian Waters" >>> To: "amber-dev" >>> Sent: Friday, April 29, 2022 4:31:40 AM >>> Subject: Contributing to the Amber docs >>> Hi all, >>> >>> Are the drafts in the amber docs ( >>> https://github.com/openjdk/amber-docs/tree/master/eg-drafts) open for >>> suggestions for new language syntax and features? >>> >>> best regards, >>> Julian From tanksherman27 at gmail.com Thu May 5 01:37:37 2022 From: tanksherman27 at gmail.com (Julian Waters) Date: Thu, 5 May 2022 09:37:37 +0800 Subject: Contributing to the Amber docs In-Reply-To: <29b729b3-c08e-5fb9-e17e-7f5a20ef7322@oracle.com> References: <2047526974.18306549.1651215300425.JavaMail.zimbra@u-pem.fr> <29b729b3-c08e-5fb9-e17e-7f5a20ef7322@oracle.com> Message-ID: Ah, I see. Thanks for the input Brian, I'll keep that in mind best regards, Julian On Thu, May 5, 2022 at 3:40 AM Brian Goetz wrote: > The eg-drafts folder exists as a place for attachments to the EG list, for > things that can't be represented as inline text (e.g., PDF files, images.) > > I think one of the reasons you've had trouble getting feedback is that you > are presenting *solutions* before presenting a clear explanation of the > problem you are trying to solve, and then getting caught up in the surface > details of the solution. First, you need to convince people there is a > problem; then you need to convince them that its a problem that needs to be > solved in the language; only then can you start to explore potential > solutions (usually not just one) in the language. > > > > On 4/29/2022 7:24 AM, Julian Waters wrote: > > Darn, that's a bummer. Thanks for the reply though, I'll see what I can do > with just the mailing list for now to get feedback on anything new (The > last one seems to be the hard part) > > best regards, > Julian > > On Fri, Apr 29, 2022 at 2:55 PM Remi Forax wrote: > > > Nope :) > > You have to convince people first by posting your issue/feature on the > mailing list. > > The best is to first write a blog post, gather opinions on it and then > post the result on the mailing list, because the bar to introduce a feature > in Java is quite high so it's very important to have a very good the first > draft. > > regards, > R?mi > > ----- Original Message ----- > > From: "Julian Waters" > To: "amber-dev" > Sent: Friday, April 29, 2022 4:31:40 AM > Subject: Contributing to the Amber docs > > Hi all, > > Are the drafts in the amber docs (https://github.com/openjdk/amber-docs/tree/master/eg-drafts) open for > suggestions for new language syntax and features? > > best regards, > Julian > > > From james.laskey at oracle.com Sat May 7 14:52:33 2022 From: james.laskey at oracle.com (Jim Laskey) Date: Sat, 7 May 2022 14:52:33 +0000 Subject: TemplatedString feedback and extension - logging use-case, lazy values, blocks In-Reply-To: References: Message-ID: [OOTO] I?ve already done examples of all of the items in your list (all good), but i?m glad you brought these topics up for open discussion. I?ll sit back a see what others have to say. However, I would like to point out that deferred evaluation requires an agreement from the policy to special case suppliers (or futures). This is something we don?t really want to bake in. Every policy should be free to pick and choose what it does. Another approach is to use policy chaining where one policy in the chain evaluates suppliers and futures to produce a new TemplatedString to be processed by the next policy in the chain. The ugliness of lambdas in an embedded expression is made worse by the fact it has to be cast to Supplier or Future since the default type for embedded expressions is Object. In all my examples, I?ve out of lined these types of expressions and used a local or better yet a global constant variable (ex., TIME_STAMP) to use in the embedded expression. I think this technique makes the string template read better. When I?m back the end of next week I can post some examples. Cheers, ?Jim After this response we should move the discussion to the amber-dev mailing list. ?? > On May 7, 2022, at 10:58 AM, Adam Juraszek wrote: > > ?The TemplatedString proposal is amazing; I can see using it in many places. > I have a few comments that I have not seen addressed. > > Logging > ======= > > One place where some kind of string templating is often used is logging. > Most logging frameworks use a pair of braces {}. TemplatedStrings have the > potential to revolutionize it. > > Log record usually consists of the following parts that the programmer > needs to provide: > * log level (such as error, info) > * message (such as "Oh no, the value {} is negative") > * parameters (such as -42) > * exception/throwable (only sometimes) > > Let's assume that TemplatedString is performant and virtually free, then > these options would be possible. I cannot say which syntax the logging > frameworks would choose: > > logger.error."Oh no, the value \{value} is negative\{ex}"; > logger.error("Oh no, the value \{value} is negative", ex); > logger."Oh no, the value \{value} is negative".error(ex); > > Versus the existing: > > logger.error("Oh no, the value {} is negative", value, ex); > > Lazy Logging > ============ > > Some logging frameworks such as Log4j2 allow lazy evaluation for example: > > logger.trace("Number is {}", () -> getRandomNumber()); > > I must admit that doing the same using TemplatedStrings is ugly and the 6 > distinct characters introducing a lazily evaluated value are impractical: > > logger.trace("Number is \{() -> getRandomNumber()}"); > > Can we offer something better using TemplatedStrings? The JEP already talks > about evaluation. For example, this is valid: > > int index; > String data = STR."\{index=0}, \{index++}"; > > and mentions that the TemplatedString provides a list of values: > > TemplatedString ts = "\{x} plus \{y} equals \{x + y}"; > List values = ts.values(); > > I want to propose an additional syntax. The escape sequence \( would denote > a lazily computed value - probably stored as a Supplier. Because the value > may not be evaluated, it must only refer to effectively final variables > (the same rules as for lambda expressions apply). > > The following should be illegal: > > int index; > String data = STR."\(index=0), \(index++)"; > > The logging use-case could be expressed more naturally: > > logger.trace("Number is \(getRandomNumber())"); > > I personally can see a parallel between \(...) and ()-> as both use > parentheses. This unfortunately is the exact opposite of the syntax of > closures in Groovy, where {123} is "lazy" and (123) produces the value > immediately. Different languages, different choices. > > When should this lazy expression be evaluated, and who is responsible for > it? It can either be: > * when ts.values() is called; > * when the value is accessed (via ts.get(N) or .next() on the List's > Iterator); > * manually when it is consumed by the author of the policy using an > instanceof check for Supplier. > > I keep this as an open question (first we need to find out if this is even > a useful feature). It also remains open what to do when a user provides a > Supplier manually: > > Supplier s = () -> 123 > logger.trace("Number is \{s} \(s)"); > > Another way to express a Supplier is via a method reference, which should > also be supported and would suggest the equivalence of the following: > > logger.trace("Number is \{this::getRandomNumber}"); > logger.trace("Number is \(this.getRandomNumber())"); > > Blocks > ====== > > What if the computation of the value requires multiple statements? > > STR."The intersection of \{a} and \{b} is \{((Supplier) ()-> {var c = new > HashSet<>(a); c.retainAll(b); return c;}).get()}" > STR."The intersection of \{a} and \{b} is \{switch(1){default:var c = new > HashSet<>(a); c.retainAll(b); yield c;}}" > > These are (as far as my imagination goes) the easiest ways to turn a block > into an expression. > > What if we introduced a special syntax for blocks? One option would be > simply doubling the braces: > > STR."The intersection of \{a} and \{b} is \{{var c = new HashSet<>(a); > c.retainAll(b); yield c;}}" > > The last closing brace is probably not needed but keeping them balanced > will help dumb editors with highlighting. > > This could be a more general syntax useful even outside TemplatedStrings > (especially in final field initialization): > > var intersection = {var c = new HashSet<>(a); c.retainAll(b); yield c;}; > > Lazy Blocks > =========== > > We may want to combine the two proposed features and compute the > intersection lazily when logging: > > logger.trace("The intersection of \{a} and \{b} is \({var c = new > HashSet<>(a); c.retainAll(b); return c;})") > > which would be equivalent to this already supported syntax provided that > the policy understands Supplier values: > > logger.trace("The intersection of \{a} and \{b} is \{() -> {var c = new > HashSet<>(a); c.retainAll(b); return c;}}") > > If we introduce \( as described in Lazy Logging above and say that > \(expression) is equivalent to \{() -> expression}, then \({statements}) > would naturally be equivalent to \{() -> {statements}}. > > Literals > ======== > > It was already mentioned in the JEP that it is possible to create a policy > returning a JSON object. We can also think of them as a way to express > literals. Some Java types already have a way to parse a String. Some > examples are: > > DURATION."PT20.345S" > Duration.from("PT20.345S") > > COMPLEX."1+3i" > Complex.from(1, 3) > > US_DATE."7/4/2022" > LocalDate.of(2022, 7, 4) > > This is almost as powerful as what C++ offers ( > https://en.cppreference.com/w/cpp/language/user_literal). > > Conclusion > ========== > > I am very happy with the current proposal and tried to explore some > possible extensions. I proposed a syntax for lazy evaluation of values, > blocks as values, and a combination thereof. I would like to hear from you, > whether this is something worth considering, and whether there is demand > for it. > > Regards > Adam From sarma.swaranga at gmail.com Mon May 9 05:44:34 2022 From: sarma.swaranga at gmail.com (Swaranga Sarma) Date: Sun, 8 May 2022 22:44:34 -0700 Subject: Specialized methods for a generic class based on type argument Message-ID: Apologies if this is not the right mailing list; I did not find a dedicated mailing list for generics and Amber deals with language level improvements hence posting it here. I want to be able to write specialized methods based on the type argument for my generic class. Using the List interface as an example: interface List { _if_T_instanceof_Number Number sum() { T sum = 0; for (T t : this) { sum += t; } return sum; } } The above method can be called only if the list instance is of type List and if the reference type is of raw type then these methods should not be visible at the call site. There are other compatibility concerns like what if a subinterface or an implementing class defines these methods but I am of course leaving these to the experts to deal with. ;-) Is this something that might be possible in the future? Regards Swaranga From amaembo at gmail.com Mon May 9 07:24:58 2022 From: amaembo at gmail.com (Tagir Valeev) Date: Mon, 9 May 2022 09:24:58 +0200 Subject: Specialized methods for a generic class based on type argument In-Reply-To: References: Message-ID: Your sample would not work anyway, as you cannot apply `+=` to any number. It's applicable to primitive types only known at compilation time. This could be useful for stream.sorted() (which de-facto works only for streams having comparable elements). Syntactically (bikeshedding, yeah!), we might adapt the receiver parameter for this purpose, allowing to specify potentially convertible type there: interface Stream { Stream sorted(Stream> this); } So the method call is allowed only when the specified receiver type is assignable from the actual receiver type. Such a change would require encoding in class files. If I understand correctly, currently there's no good place where this information could be recorded. So probably a new attribute will be required in class files, which requires changes in JVM specification. Also, changes in reflection API to support retrieving this information reflectively. Also, changes in javax.model API to support annotation processors. Quite a lot of work for a feature with limited usefulness. A typical work-around in current Java is to use a static method for this purpose. With best regards, Tagir Valeev On Mon, May 9, 2022 at 7:45 AM Swaranga Sarma wrote: > > Apologies if this is not the right mailing list; I did not find a dedicated > mailing list for generics and Amber deals with language level improvements > hence posting it here. > > I want to be able to write specialized methods based on the type argument > for my generic class. Using the List interface as an example: > > interface List { > _if_T_instanceof_Number Number sum() { > T sum = 0; > for (T t : this) { > sum += t; > } > return sum; > } > } > > The above method can be called only if the list instance is of type List extends Number> and if the reference type is of raw type then these methods > should not be visible at the call site. There are other compatibility > concerns like what if a subinterface or an implementing class defines these > methods but I am of course leaving these to the experts to deal with. ;-) > > Is this something that might be possible in the future? > > Regards > Swaranga From nlisker at gmail.com Mon May 9 13:47:09 2022 From: nlisker at gmail.com (Nir Lisker) Date: Mon, 9 May 2022 16:47:09 +0300 Subject: Specialized methods for a generic class based on type argument In-Reply-To: References: Message-ID: This thread from the Valhalla dev list might be of interest: https://mail.openjdk.java.net/pipermail/valhalla-dev/2021-August/009530.html - Nir From brian.goetz at oracle.com Mon May 9 13:51:57 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 9 May 2022 09:51:57 -0400 Subject: Specialized methods for a generic class based on type argument In-Reply-To: References: Message-ID: What you are suggesting is what we call a _conditional method_; a method that is a member of some parameterizations but not others.? This is an entirely reasonable concept, and we've given extensive thought to the topic under Project Valhalla, but there are a number of challenges that mean such things are at least a few steps away: ?- It is not merely enough to say that sum() is only a member of List for certain T; as your example shows, you would like to be able to get at the zero value for T, and its addition function.? (More specifically, sum() works when T is a *monoid*, and the implementation of sum() requires access to a *witness* to it's monoid-hood.) ?- Said witnessing requires some degree of runtime reification, either in the form of direct reification of generic type parameters (as C# does), or the use of implicit arguments (as Scala does.) ?- As your example suggests, even if you had the above, you probably would soon be wondering/asking/complaining "why can't I say `+`, rather than calling `add`", so in addition to asking for reified generics and witnesses to type classes, you are also asking for operator overloading. None of the above are out of the question, but each are obviously a significant lift, so don't expect it to fall out as an "Amber-sized" feature.? More like "Add generics to Java". On 5/9/2022 1:44 AM, Swaranga Sarma wrote: > Apologies if this is not the right mailing list; I did not find a dedicated > mailing list for generics and Amber deals with language level improvements > hence posting it here. > > I want to be able to write specialized methods based on the type argument > for my generic class. Using the List interface as an example: > > interface List { > _if_T_instanceof_Number Number sum() { > T sum = 0; > for (T t : this) { > sum += t; > } > return sum; > } > } > > The above method can be called only if the list instance is of type List extends Number> and if the reference type is of raw type then these methods > should not be visible at the call site. There are other compatibility > concerns like what if a subinterface or an implementing class defines these > methods but I am of course leaving these to the experts to deal with. ;-) > > Is this something that might be possible in the future? > > Regards > Swaranga From sarma.swaranga at gmail.com Mon May 9 17:25:33 2022 From: sarma.swaranga at gmail.com (Swaranga Sarma) Date: Mon, 9 May 2022 10:25:33 -0700 Subject: Specialized methods for a generic class based on type argument In-Reply-To: References: Message-ID: >More like "Add generics to Java". Ouch! :-) Thank you for the responses. Regards Swaranga On Mon, May 9, 2022 at 6:53 AM Brian Goetz wrote: > What you are suggesting is what we call a _conditional method_; a method > that is a member of some parameterizations but not others. This is an > entirely reasonable concept, and we've given extensive thought to the topic > under Project Valhalla, but there are a number of challenges that mean such > things are at least a few steps away: > > - It is not merely enough to say that sum() is only a member of List > for certain T; as your example shows, you would like to be able to get at > the zero value for T, and its addition function. (More specifically, sum() > works when T is a *monoid*, and the implementation of sum() requires access > to a *witness* to it's monoid-hood.) > > - Said witnessing requires some degree of runtime reification, either in > the form of direct reification of generic type parameters (as C# does), or > the use of implicit arguments (as Scala does.) > > - As your example suggests, even if you had the above, you probably would > soon be wondering/asking/complaining "why can't I say `+`, rather than > calling `add`", so in addition to asking for reified generics and witnesses > to type classes, you are also asking for operator overloading. > > None of the above are out of the question, but each are obviously a > significant lift, so don't expect it to fall out as an "Amber-sized" > feature. More like "Add generics to Java". > > > > On 5/9/2022 1:44 AM, Swaranga Sarma wrote: > > Apologies if this is not the right mailing list; I did not find a dedicated > mailing list for generics and Amber deals with language level improvements > hence posting it here. > > I want to be able to write specialized methods based on the type argument > for my generic class. Using the List interface as an example: > > interface List { > _if_T_instanceof_Number Number sum() { > T sum = 0; > for (T t : this) { > sum += t; > } > return sum; > } > } > > The above method can be called only if the list instance is of type List extends Number> and if the reference type is of raw type then these methods > should not be visible at the call site. There are other compatibility > concerns like what if a subinterface or an implementing class defines these > methods but I am of course leaving these to the experts to deal with. ;-) > > Is this something that might be possible in the future? > > Regards > Swaranga > > > From tanksherman27 at gmail.com Thu May 12 03:56:25 2022 From: tanksherman27 at gmail.com (Julian Waters) Date: Thu, 12 May 2022 11:56:25 +0800 Subject: Discussion on boilerplate for code tightly bound to OOP Message-ID: (cc'ed to amber-spec-observers at openjdk.java.net) Hey everyone, Previously I submitted a post for discussing potential new ways to reduce boilerplate in Java for all sorts of applications, ranging from simple experiments a newcomer to the language may create to large and complex systems in production ( https://mail.openjdk.java.net/pipermail/amber-dev/2022-April/007303.html). It's been pointed out to me that the issue hasn't been properly defined though, so I'd like to discuss that here instead, without jumping the gun like in the previous post. The problem is something that Java's had for quite some time, and is one of the main issues that earns it quite a bad reputation[1] amongst developers (Even if some of that disdain is outdated and has already been rectified in newer Java releases)- The language is simply too verbose/has too much boilerplate, and Java code itself is too tightly linked to classes[1] (at least on the semantic level; Languages like Kotlin support other paradigms like functional code even though they ultimately compile down to Java classfiles). Of course, like I did mention above, quite a number of these issues are already being tackled (records, accessors for arbitrary classes, talks about deconstructors, with expressions, future reified generics, potential operator overloading in Valhalla with values and primitives), which is a great thing, but Java is still having trouble catching up with modern developer expectations and other languages as a whole. While many of these complaints are often fueled by hype and personal opinion and provide no constructive value as feedback whatsoever, there are still many salient points to address, in this case it's how the language is too forceful with OOP. It's not much of a secret that if one wants to write simple, procedural Java, they have to fight the language substantially to do so. Classes have to be marked as final, their constructors privated and made to throw an exception if reflection is used on them anyway, and every method has to be marked as static. At first glance, this might just seem like a mild inconvenience and one would wonder why this is an issue, but this becomes incredibly repetitive and frustrating when made to scale, which will undoubtedly drive developers off when considering it as a language of choice (Even with a simple "Hello World" jar, many newcomers are often put off with how much code is required, which is the last thing we'd want to happen). Much of this extra verbosity from having procedural code (unlike with statically typing a variable or a method's return type for instance) is also a tradeoff without any benefit as well, and there doesn't seem to be much of a reason to make developers pay such heavy penalties if we can help them out with new language features, even more so since "mutating" value classes was already considered too painful in the amber drafts (Also from[2] "You get more typing practice?" "The shares for keyboard manufacturers go up?" "You can sell software to generate this automatically?"). It could be argued that this could all be avoided by embracing OOP entirely, which is certainly one of Java's biggest strengths, but there are many instances where the procedural approach would be significantly easier to implement the program's logic in (think the number of Java codebases that have static only utility classes), or where forcing OOP would actually degrade both code quality and performance (deeply nested chains of object pointers or pointless object allocations for instance). There's also another problem in that this forceful approach also causes newer developers to learn rather harmful and over-the-top notions of OOP and how everything must be a class and object- To the point that there is even an entire talk for how to unlearn the rigid and incorrect concepts of OOP for people coming to other languages from Java[3]. Given how popular of a language Java already is despite the number of complaints against it, we should be careful to not be the ones responsible for "poisoning" individuals new to programming with the idea that everything has to be in a class- This does no one any favours, not us, nor the learning programmers who may end up harming the ecosystem at large once their assumptions are deeply rooted. Maybe Java's current verbosity when it comes to procedural code could still be an advantage for enterprise applications, where being as explicit as possible with boilerplate can be helpful when reading a codebase, but that would be a rather shortsighted view. It would be favourable for Java to become a well liked choice for general purpose utilities such as regular desktop applications or quick and small ones written out of convenience for instance, and limiting it to enterprise-only would make its ideal users only a very specific group- Not good if we want it to stay competitive. Not to mention that a new language feature for facilitating procedural code can indeed be explicit without much of the boilerplate of static only classes, if designed properly. This would also help in hiding the implementation detail of ultimately compiling down to classfiles, bypassing the issue mentioned above entirely, since to the developer there is technically no class involved whatsoever- Just methods. You could even get IDE support for checking whether reflection attempted to call a constructor at compile time too! In the end, it certainly doesn't help with Java's image (and potential newcomers to the language) that a common view of it by many developers out there is that it's reluctant to include newer language features because " you, I'm Java"[2], which is a reputation we can do without, and I feel that this discussion would be a good place to start in dispelling that view of Java that many developers have today. best regards, Julian P.S. On an unrelated note, personally I feel that it'd be slightly better if "with" expressions were declared with a -> or with any other existing keyword instead of reserving an entirely new one for this purpose. I digress however, this is just a personal viewpoint. Links: [1] https://www.youtube.com/watch?v=m4-HM_sCvtQ&ab_channel=Fireship [2] https://www.reddit.com/r/learnprogramming/comments/1cv8rb/why_does_java_get_such_a_bad_rep/ [3] https://www.youtube.com/watch?v=o9pEzgHorH0 From brian.goetz at oracle.com Thu May 12 12:33:04 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 12 May 2022 08:33:04 -0400 Subject: Discussion on boilerplate for code tightly bound to OOP In-Reply-To: References: Message-ID: <369d5861-df6c-9768-d764-4ad67f2babfb@oracle.com> Yes, this is well understood. OOP is useful for many things; we don't need to force everything through it.? As you point out, we've made some steps in this direction, and will likely make more. Some obvious ones that have been raised a bazillion times: ?- "Why do I need a public class Foo / public static void main" to write Hello World?? This one is a substantial concern for both "script developers" and educators.? The single-source-file launcher was a help, but really, people want "Hello World" to be a one-liner.? This is reasonable, but as always, the volume of detail here is significant.? (Though if you look closely, we've already started this work; have you noticed the gradual regularization of "nest X in Y" changes, such as local interfaces, and "static members in inner classes"?? These are paving the way to this.) ?- A better way to define singletons.? Right now, "singleton" is just a pattern; Scala, for example, raises this to a language feature ("object Foo { }").? In such a world, your procedural program is just an object. These are all realistic candidates, and ones we've spent some time scoping out.? But as always, we can realistically only do about 1% of the "reasonable features you could imagine", so we have to prioritize aggressively. On 5/11/2022 11:56 PM, Julian Waters wrote: > (cc'ed toamber-spec-observers at openjdk.java.net) > Hey everyone, > > Previously I submitted a post for discussing potential new ways to reduce > boilerplate in Java for all sorts of applications, ranging from simple > experiments a newcomer to the language may create to large and complex > systems in production ( > https://mail.openjdk.java.net/pipermail/amber-dev/2022-April/007303.html). > It's been pointed out to me that the issue hasn't been properly defined > though, so I'd like to discuss that here instead, without jumping the gun > like in the previous post. > > The problem is something that Java's had for quite some time, and is one of > the main issues that earns it quite a bad reputation[1] amongst developers > (Even if some of that disdain is outdated and has already been rectified in > newer Java releases)- The language is simply too verbose/has too much > boilerplate, and Java code itself is too tightly linked to classes[1] (at > least on the semantic level; Languages like Kotlin support other paradigms > like functional code even though they ultimately compile down to Java > classfiles). > > Of course, like I did mention above, quite a number of these issues are > already being tackled (records, accessors for arbitrary classes, talks > about deconstructors, with expressions, future reified generics, potential > operator overloading in Valhalla with values and primitives), which is a > great thing, but Java is still having trouble catching up with modern > developer expectations and other languages as a whole. While many of these > complaints are often fueled by hype and personal opinion and provide no > constructive value as feedback whatsoever, there are still many salient > points to address, in this case it's how the language is too forceful with > OOP. > > It's not much of a secret that if one wants to write simple, procedural > Java, they have to fight the language substantially to do so. Classes have > to be marked as final, their constructors privated and made to throw an > exception if reflection is used on them anyway, and every method has to be > marked as static. At first glance, this might just seem like a mild > inconvenience and one would wonder why this is an issue, but this becomes > incredibly repetitive and frustrating when made to scale, which will > undoubtedly drive developers off when considering it as a language of > choice (Even with a simple "Hello World" jar, many newcomers are often put > off with how much code is required, which is the last thing we'd want to > happen). Much of this extra verbosity from having procedural code (unlike > with statically typing a variable or a method's return type for instance) > is also a tradeoff without any benefit as well, and there doesn't seem to > be much of a reason to make developers pay such heavy penalties if we can > help them out with new language features, even more so since "mutating" > value classes was already considered too painful in the amber drafts (Also > from[2] "You get more typing practice?" "The shares for keyboard > manufacturers go up?" "You can sell software to generate this omitted> automatically?"). > > It could be argued that this could all be avoided by embracing OOP > entirely, which is certainly one of Java's biggest strengths, but there are > many instances where the procedural approach would be significantly easier > to implement the program's logic in (think the number of Java codebases > that have static only utility classes), or where forcing OOP would actually > degrade both code quality and performance (deeply nested chains of object > pointers or pointless object allocations for instance). There's also > another problem in that this forceful approach also causes newer developers > to learn rather harmful and over-the-top notions of OOP and how everything > must be a class and object- To the point that there is even an entire talk > for how to unlearn the rigid and incorrect concepts of OOP for people > coming to other languages from Java[3]. Given how popular of a language > Java already is despite the number of complaints against it, we should be > careful to not be the ones responsible for "poisoning" individuals new to > programming with the idea that everything has to be in a class- This does > no one any favours, not us, nor the learning programmers who may end up > harming the ecosystem at large once their assumptions are deeply rooted. > > Maybe Java's current verbosity when it comes to procedural code could still > be an advantage for enterprise applications, where being as explicit as > possible with boilerplate can be helpful when reading a codebase, but that > would be a rather shortsighted view. It would be favourable for Java to > become a well liked choice for general purpose utilities such as regular > desktop applications or quick and small ones written out of convenience for > instance, and limiting it to enterprise-only would make its ideal users > only a very specific group- Not good if we want it to stay competitive. Not > to mention that a new language feature for facilitating procedural code can > indeed be explicit without much of the boilerplate of static only > classes, if designed properly. This would also help in hiding the > implementation detail of ultimately compiling down to classfiles, bypassing > the issue mentioned above entirely, since to the developer there is > technically no class involved whatsoever- Just methods. You could even get > IDE support for checking whether reflection attempted to call a constructor > at compile time too! > > In the end, it certainly doesn't help with Java's image (and potential > newcomers to the language) that a common view of it by many developers out > there is that it's reluctant to include newer language features > because " omitted> you, I'm Java"[2], which is a reputation we can do without, and I > feel that this discussion would be a good place to start in dispelling that > view of Java that many developers have today. > > best regards, > Julian > > P.S. On an unrelated note, personally I feel that it'd be slightly better > if "with" expressions were declared with a -> or with any other existing > keyword instead of reserving an entirely new one for this purpose. I > digress however, this is just a personal viewpoint. > > Links: > [1]https://www.youtube.com/watch?v=m4-HM_sCvtQ&ab_channel=Fireship > [2] > https://www.reddit.com/r/learnprogramming/comments/1cv8rb/why_does_java_get_such_a_bad_rep/ > [3]https://www.youtube.com/watch?v=o9pEzgHorH0 From michal at kleczek.org Thu May 12 13:12:31 2022 From: michal at kleczek.org (=?utf-8?Q?Micha=C5=82_K=C5=82eczek?=) Date: Thu, 12 May 2022 15:12:31 +0200 Subject: Discussion on boilerplate for code tightly bound to OOP In-Reply-To: <369d5861-df6c-9768-d764-4ad67f2babfb@oracle.com> References: <369d5861-df6c-9768-d764-4ad67f2babfb@oracle.com> Message-ID: > > - A better way to define singletons. Right now, "singleton" is just a pattern; Scala, for example, raises this to a language feature ("object Foo { }"). I?ve always found metaclasses idea from Erik Allen?s et. al. paper intriguing in this regard: https://www.cs.tufts.edu/~nr/cs257/archive/neal-glew/mcrt/Fortress/p109-allen.pdf From tanksherman27 at gmail.com Thu May 12 13:15:26 2022 From: tanksherman27 at gmail.com (Julian Waters) Date: Thu, 12 May 2022 21:15:26 +0800 Subject: Discussion on boilerplate for code tightly bound to OOP In-Reply-To: <369d5861-df6c-9768-d764-4ad67f2babfb@oracle.com> References: <369d5861-df6c-9768-d764-4ad67f2babfb@oracle.com> Message-ID: I don't believe I've seen (or heard of) "the gradual regularization of "nest X in Y" changes, such as local interfaces, and "static members in inner classes"" so far, what's that about? Though "public static void main" and Hello World not being a one liner are related issues, they're more of a symptom of the actual problem- That is, although Java *can* definitely be written without OOP, it punishes the developer a bit too much for doing so. As mentioned, verbosity in this particular case isn't a tradeoff for a benefit that helps the developer, it's more just there for correctness. I'd stress that turning Java into a scripting language shouldn't be a goal however, instead the aim should just be to make it less painful to write anything that isn't OOP bound without changing Java excessively. Maybe after we've established some sort of baseline syntax (Eg a "package" class of sorts for instance) we could then tack on extra features that would ultimately let developers write better functional (Ahem, methodical?) code and make Hello Worlds a true 1 liner (The latter understandably likely doesn't have a high priority). I unfortunately don't have a good indicator of Amber's current progress, which is why I was querying about this issue initially. Out of curiosity, have there already been plans drawn out for this specific issue, or could we use this discussion as a rough start when the time comes? I can't really comment about singletons, given that I didn't have them in mind for this particular discussion. Granted, it would be interesting to see them being added as a full-on language feature though (I'm unsure if people using enums as singletons nowadays is encouraged or not). best regards, Julian On Thu, May 12, 2022 at 8:33 PM Brian Goetz wrote: > Yes, this is well understood. OOP is useful for many things; we don't > need to force everything through it. As you point out, we've made some > steps in this direction, and will likely make more. > > Some obvious ones that have been raised a bazillion times: > > - "Why do I need a public class Foo / public static void main" to write > Hello World? This one is a substantial concern for both "script > developers" and educators. The single-source-file launcher was a help, but > really, people want "Hello World" to be a one-liner. This is reasonable, > but as always, the volume of detail here is significant. (Though if you > look closely, we've already started this work; have you noticed the gradual > regularization of "nest X in Y" changes, such as local interfaces, and > "static members in inner classes"? These are paving the way to this.) > > - A better way to define singletons. Right now, "singleton" is just a > pattern; Scala, for example, raises this to a language feature ("object Foo > { }"). In such a world, your procedural program is just an object. > > These are all realistic candidates, and ones we've spent some time scoping > out. But as always, we can realistically only do about 1% of the > "reasonable features you could imagine", so we have to prioritize > aggressively. > > > > On 5/11/2022 11:56 PM, Julian Waters wrote: > > (cc'ed to amber-spec-observers at openjdk.java.net) > Hey everyone, > > Previously I submitted a post for discussing potential new ways to reduce > boilerplate in Java for all sorts of applications, ranging from simple > experiments a newcomer to the language may create to large and complex > systems in production (https://mail.openjdk.java.net/pipermail/amber-dev/2022-April/007303.html). > It's been pointed out to me that the issue hasn't been properly defined > though, so I'd like to discuss that here instead, without jumping the gun > like in the previous post. > > The problem is something that Java's had for quite some time, and is one of > the main issues that earns it quite a bad reputation[1] amongst developers > (Even if some of that disdain is outdated and has already been rectified in > newer Java releases)- The language is simply too verbose/has too much > boilerplate, and Java code itself is too tightly linked to classes[1] (at > least on the semantic level; Languages like Kotlin support other paradigms > like functional code even though they ultimately compile down to Java > classfiles). > > Of course, like I did mention above, quite a number of these issues are > already being tackled (records, accessors for arbitrary classes, talks > about deconstructors, with expressions, future reified generics, potential > operator overloading in Valhalla with values and primitives), which is a > great thing, but Java is still having trouble catching up with modern > developer expectations and other languages as a whole. While many of these > complaints are often fueled by hype and personal opinion and provide no > constructive value as feedback whatsoever, there are still many salient > points to address, in this case it's how the language is too forceful with > OOP. > > It's not much of a secret that if one wants to write simple, procedural > Java, they have to fight the language substantially to do so. Classes have > to be marked as final, their constructors privated and made to throw an > exception if reflection is used on them anyway, and every method has to be > marked as static. At first glance, this might just seem like a mild > inconvenience and one would wonder why this is an issue, but this becomes > incredibly repetitive and frustrating when made to scale, which will > undoubtedly drive developers off when considering it as a language of > choice (Even with a simple "Hello World" jar, many newcomers are often put > off with how much code is required, which is the last thing we'd want to > happen). Much of this extra verbosity from having procedural code (unlike > with statically typing a variable or a method's return type for instance) > is also a tradeoff without any benefit as well, and there doesn't seem to > be much of a reason to make developers pay such heavy penalties if we can > help them out with new language features, even more so since "mutating" > value classes was already considered too painful in the amber drafts (Also > from[2] "You get more typing practice?" "The shares for keyboard > manufacturers go up?" "You can sell software to generate this omitted> automatically?"). > > It could be argued that this could all be avoided by embracing OOP > entirely, which is certainly one of Java's biggest strengths, but there are > many instances where the procedural approach would be significantly easier > to implement the program's logic in (think the number of Java codebases > that have static only utility classes), or where forcing OOP would actually > degrade both code quality and performance (deeply nested chains of object > pointers or pointless object allocations for instance). There's also > another problem in that this forceful approach also causes newer developers > to learn rather harmful and over-the-top notions of OOP and how everything > must be a class and object- To the point that there is even an entire talk > for how to unlearn the rigid and incorrect concepts of OOP for people > coming to other languages from Java[3]. Given how popular of a language > Java already is despite the number of complaints against it, we should be > careful to not be the ones responsible for "poisoning" individuals new to > programming with the idea that everything has to be in a class- This does > no one any favours, not us, nor the learning programmers who may end up > harming the ecosystem at large once their assumptions are deeply rooted. > > Maybe Java's current verbosity when it comes to procedural code could still > be an advantage for enterprise applications, where being as explicit as > possible with boilerplate can be helpful when reading a codebase, but that > would be a rather shortsighted view. It would be favourable for Java to > become a well liked choice for general purpose utilities such as regular > desktop applications or quick and small ones written out of convenience for > instance, and limiting it to enterprise-only would make its ideal users > only a very specific group- Not good if we want it to stay competitive. Not > to mention that a new language feature for facilitating procedural code can > indeed be explicit without much of the boilerplate of static only > classes, if designed properly. This would also help in hiding the > implementation detail of ultimately compiling down to classfiles, bypassing > the issue mentioned above entirely, since to the developer there is > technically no class involved whatsoever- Just methods. You could even get > IDE support for checking whether reflection attempted to call a constructor > at compile time too! > > In the end, it certainly doesn't help with Java's image (and potential > newcomers to the language) that a common view of it by many developers out > there is that it's reluctant to include newer language features > because " omitted> you, I'm Java"[2], which is a reputation we can do without, and I > feel that this discussion would be a good place to start in dispelling that > view of Java that many developers have today. > > best regards, > Julian > > P.S. On an unrelated note, personally I feel that it'd be slightly better > if "with" expressions were declared with a -> or with any other existing > keyword instead of reserving an entirely new one for this purpose. I > digress however, this is just a personal viewpoint. > > Links: > [1] https://www.youtube.com/watch?v=m4-HM_sCvtQ&ab_channel=Fireship > [2]https://www.reddit.com/r/learnprogramming/comments/1cv8rb/why_does_java_get_such_a_bad_rep/ > [3] https://www.youtube.com/watch?v=o9pEzgHorH0 > > > From kevinb at google.com Thu May 12 15:41:33 2022 From: kevinb at google.com (Kevin Bourrillion) Date: Thu, 12 May 2022 08:41:33 -0700 Subject: Discussion on boilerplate for code tightly bound to OOP In-Reply-To: References: <369d5861-df6c-9768-d764-4ad67f2babfb@oracle.com> Message-ID: Tangent On Thu, May 12, 2022 at 6:16 AM Julian Waters wrote: Granted, it would be interesting to > see them being added as a full-on language feature though (I'm unsure if > people using enums as singletons nowadays is encouraged or not). > (Is there controversy here? What is a singleton if not an enumerated type where N == 1? I don't think we're using enums "as" singletons, they just *are* singletons until you add a second constant.) -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com From james.laskey at oracle.com Thu May 12 17:25:02 2022 From: james.laskey at oracle.com (Jim Laskey) Date: Thu, 12 May 2022 17:25:02 +0000 Subject: TemplatedString feedback and extension - logging use-case, lazy values, blocks In-Reply-To: References: Message-ID: <74E94FCF-F3A4-4101-85B0-1A6CB0B6C4DF@oracle.com> [Note that all code samples below are speculative and subject to change. There is also an assumption reader has read and has an understanding of the JEP draft. https://openjdk.java.net/jeps/8273943] I've had a chance to isolate some examples to demonstrate an approach for deferred evaluation using suppliers. Starting with the general pattern for a (string result) policy as follows; static final StringPolicy MY_SP = ts -> { StringBuilder sb = new StringBuilder(); Iterator fragmentsIter = ts.fragments().iterator(); for (Object value : ts.values()) { sb.append(fragmentsIter.next()); sb.append(value); } sb.append(fragmentsIter.next()); return sb.toString(); }; ... int x = 10, y = 20; String result = MY_SP."\{x} + \{y} = \{x + y}"; // The result will be "10 + 20 = 30". To introduce deferred evaluation we can add a test for java.util.function.Supplier and use the supplier's value instead; static final StringPolicy MY_SP = ts -> { StringBuilder sb = new StringBuilder(); Iterator fragmentsIter = ts.fragments().iterator(); for (Object value : ts.values()) { sb.append(fragmentsIter.next()); //------------------------------------------ if (value instanceof Supplier supplier) { sb.append(supplier.get()); } else { sb.append(value); } //------------------------------------------ } sb.append(fragmentsIter.next()); return sb.toString(); }; ... static final DateTimeFormatter HH_MM_SS = DateTimeFormatter.ofPattern("HH:mm:ss"); static final Supplier TIME_STAMP = () -> LocalDateTime.now().toLocalTime().format(HH_MM_SS); ... String message = MY_SP."\{TIME_STAMP}: Received response"; // The result will be "13:56:48: Received response". In this case, the formatting of the time stamp is deferred until the policy is applied. This example can be expanded to test for java.util.concurrent.Future and java.util.concurrent.FutureTask. The advantage of futures over Supplier is that they are only evaluated once; static final StringPolicy MY_SP = ts -> { StringBuilder sb = new StringBuilder(); Iterator fragmentsIter = ts.fragments().iterator(); for (Object value : ts.values()) { sb.append(fragmentsIter.next()); //------------------------------------------ if (value instanceof Future future) { if (future instanceof FutureTask task) { task.run(); } try { sb.append(future.get()); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } else if (value instanceof Supplier supplier) { sb.append(supplier.get()); } else { sb.append(value); } //------------------------------------------ } sb.append(fragmentsIter.next()); return sb.toString(); }; To answer Attila's comments. 1) Nothing is computed unless the policy performs an action, so a logging policy that has an if(ENABLED) around it's main code and static final ENABLED = false; will have little or no overhead. 2) One of the primary goals of template policies is to screen inputs to prevent injection attacks. So a well written policy should not be any more susceptible that a println statement. There is no interpretation or a need for interpretation in string templates. Policies may add interpretation, but then the onus is on the policy Cheers, -- Jim On May 7, 2022, at 11:52 AM, Jim Laskey > wrote: [OOTO] I?ve already done examples of all of the items in your list (all good), but i?m glad you brought these topics up for open discussion. I?ll sit back a see what others have to say. However, I would like to point out that deferred evaluation requires an agreement from the policy to special case suppliers (or futures). This is something we don?t really want to bake in. Every policy should be free to pick and choose what it does. Another approach is to use policy chaining where one policy in the chain evaluates suppliers and futures to produce a new TemplatedString to be processed by the next policy in the chain. The ugliness of lambdas in an embedded expression is made worse by the fact it has to be cast to Supplier or Future since the default type for embedded expressions is Object. In all my examples, I?ve out of lined these types of expressions and used a local or better yet a global constant variable (ex., TIME_STAMP) to use in the embedded expression. I think this technique makes the string template read better. When I?m back the end of next week I can post some examples. Cheers, ?Jim After this response we should move the discussion to the amber-dev mailing list. ?? On May 7, 2022, at 10:58 AM, Adam Juraszek > wrote: ?The TemplatedString proposal is amazing; I can see using it in many places. I have a few comments that I have not seen addressed. Logging ======= One place where some kind of string templating is often used is logging. Most logging frameworks use a pair of braces {}. TemplatedStrings have the potential to revolutionize it. Log record usually consists of the following parts that the programmer needs to provide: * log level (such as error, info) * message (such as "Oh no, the value {} is negative") * parameters (such as -42) * exception/throwable (only sometimes) Let's assume that TemplatedString is performant and virtually free, then these options would be possible. I cannot say which syntax the logging frameworks would choose: logger.error."Oh no, the value \{value} is negative\{ex}"; logger.error("Oh no, the value \{value} is negative", ex); logger."Oh no, the value \{value} is negative".error(ex); Versus the existing: logger.error("Oh no, the value {} is negative", value, ex); Lazy Logging ============ Some logging frameworks such as Log4j2 allow lazy evaluation for example: logger.trace("Number is {}", () -> getRandomNumber()); I must admit that doing the same using TemplatedStrings is ugly and the 6 distinct characters introducing a lazily evaluated value are impractical: logger.trace("Number is \{() -> getRandomNumber()}"); Can we offer something better using TemplatedStrings? The JEP already talks about evaluation. For example, this is valid: int index; String data = STR."\{index=0}, \{index++}"; and mentions that the TemplatedString provides a list of values: TemplatedString ts = "\{x} plus \{y} equals \{x + y}"; List values = ts.values(); I want to propose an additional syntax. The escape sequence \( would denote a lazily computed value - probably stored as a Supplier. Because the value may not be evaluated, it must only refer to effectively final variables (the same rules as for lambda expressions apply). The following should be illegal: int index; String data = STR."\(index=0), \(index++)"; The logging use-case could be expressed more naturally: logger.trace("Number is \(getRandomNumber())"); I personally can see a parallel between \(...) and ()-> as both use parentheses. This unfortunately is the exact opposite of the syntax of closures in Groovy, where {123} is "lazy" and (123) produces the value immediately. Different languages, different choices. When should this lazy expression be evaluated, and who is responsible for it? It can either be: * when ts.values() is called; * when the value is accessed (via ts.get(N) or .next() on the List's Iterator); * manually when it is consumed by the author of the policy using an instanceof check for Supplier. I keep this as an open question (first we need to find out if this is even a useful feature). It also remains open what to do when a user provides a Supplier manually: Supplier s = () -> 123 logger.trace("Number is \{s} \(s)"); Another way to express a Supplier is via a method reference, which should also be supported and would suggest the equivalence of the following: logger.trace("Number is \{this::getRandomNumber}"); logger.trace("Number is \(this.getRandomNumber())"); Blocks ====== What if the computation of the value requires multiple statements? STR."The intersection of \{a} and \{b} is \{((Supplier) ()-> {var c = new HashSet<>(a); c.retainAll(b); return c;}).get()}" STR."The intersection of \{a} and \{b} is \{switch(1){default:var c = new HashSet<>(a); c.retainAll(b); yield c;}}" These are (as far as my imagination goes) the easiest ways to turn a block into an expression. What if we introduced a special syntax for blocks? One option would be simply doubling the braces: STR."The intersection of \{a} and \{b} is \{{var c = new HashSet<>(a); c.retainAll(b); yield c;}}" The last closing brace is probably not needed but keeping them balanced will help dumb editors with highlighting. This could be a more general syntax useful even outside TemplatedStrings (especially in final field initialization): var intersection = {var c = new HashSet<>(a); c.retainAll(b); yield c;}; Lazy Blocks =========== We may want to combine the two proposed features and compute the intersection lazily when logging: logger.trace("The intersection of \{a} and \{b} is \({var c = new HashSet<>(a); c.retainAll(b); return c;})") which would be equivalent to this already supported syntax provided that the policy understands Supplier values: logger.trace("The intersection of \{a} and \{b} is \{() -> {var c = new HashSet<>(a); c.retainAll(b); return c;}}") If we introduce \( as described in Lazy Logging above and say that \(expression) is equivalent to \{() -> expression}, then \({statements}) would naturally be equivalent to \{() -> {statements}}. Literals ======== It was already mentioned in the JEP that it is possible to create a policy returning a JSON object. We can also think of them as a way to express literals. Some Java types already have a way to parse a String. Some examples are: DURATION."PT20.345S" Duration.from("PT20.345S") COMPLEX."1+3i" Complex.from(1, 3) US_DATE."7/4/2022" LocalDate.of(2022, 7, 4) This is almost as powerful as what C++ offers ( https://en.cppreference.com/w/cpp/language/user_literal). Conclusion ========== I am very happy with the current proposal and tried to explore some possible extensions. I proposed a syntax for lazy evaluation of values, blocks as values, and a combination thereof. I would like to hear from you, whether this is something worth considering, and whether there is demand for it. Regards Adam From attila.kelemen85 at gmail.com Thu May 12 18:07:33 2022 From: attila.kelemen85 at gmail.com (Attila Kelemen) Date: Thu, 12 May 2022 20:07:33 +0200 Subject: TemplatedString feedback and extension - logging use-case, lazy values, blocks In-Reply-To: <74E94FCF-F3A4-4101-85B0-1A6CB0B6C4DF@oracle.com> References: <74E94FCF-F3A4-4101-85B0-1A6CB0B6C4DF@oracle.com> Message-ID: It seems I did too much extrapolation after sloppy reading. Sorry for that. Anyway, this seems nice. Just one thing (which is partially mentioned in the alternatives section of the JEP). The syntax looks a bit bizarre to me (even though it isn't ambiguous), because this adds an additional conceptual weight to the language given that it looks like as if the templated string instance is the method name. In fact, the syntax `MY_POLICY."\{x};\{y}"` could be moved out from this JEP, and simply provide a method both ways: - `TemplatedString.format(TemplatePolicy)` - `TemplatePolicy.apply(TemplatedString)` (already in JEP). If we ever needed the JEP provided sugar, then I would try with something more generic that would benefit other things as well consistently, not just special case string templates. Since as I can see the main benefit of the syntax is that we don't need to bother with parenthesis. But otherwise, this feature would be indeed very useful. Though, I can already see people abusing a TO_STRING policy (which will surely be provided in commons-lang or similar, if not the JDK) :) Attila Jim Laskey ezt ?rta (id?pont: 2022. m?j. 12., Cs, 19:25): > > [Note that all code samples below are speculative and subject to change. There is also an assumption reader has read and has an understanding of the JEP draft. https://openjdk.java.net/jeps/8273943] > > I've had a chance to isolate some examples to demonstrate an approach for deferred evaluation using suppliers. > > Starting with the general pattern for a (string result) policy as follows; > > static final StringPolicy MY_SP = ts -> { > StringBuilder sb = new StringBuilder(); > Iterator fragmentsIter = ts.fragments().iterator(); > for (Object value : ts.values()) { > sb.append(fragmentsIter.next()); > sb.append(value); > } > sb.append(fragmentsIter.next()); > return sb.toString(); > }; > > ... > > int x = 10, y = 20; > String result = MY_SP."\{x} + \{y} = \{x + y}"; // The result will be "10 + 20 = 30". > > To introduce deferred evaluation we can add a test for java.util.function.Supplier and > use the supplier's value instead; > > static final StringPolicy MY_SP = ts -> { > StringBuilder sb = new StringBuilder(); > Iterator fragmentsIter = ts.fragments().iterator(); > for (Object value : ts.values()) { > sb.append(fragmentsIter.next()); > //------------------------------------------ > if (value instanceof Supplier supplier) { > sb.append(supplier.get()); > } else { > sb.append(value); > } > //------------------------------------------ > } > sb.append(fragmentsIter.next()); > return sb.toString(); > }; > > ... > > static final DateTimeFormatter HH_MM_SS = DateTimeFormatter.ofPattern("HH:mm:ss"); > static final Supplier TIME_STAMP = () -> LocalDateTime.now().toLocalTime().format(HH_MM_SS); > > ... > > String message = MY_SP."\{TIME_STAMP}: Received response"; // The result will be "13:56:48: Received response". > > In this case, the formatting of the time stamp is deferred until the policy is applied. This example can be expanded to test for > java.util.concurrent.Future and java.util.concurrent.FutureTask. The advantage of futures > over Supplier is that they are only evaluated once; > > static final StringPolicy MY_SP = ts -> { > StringBuilder sb = new StringBuilder(); > Iterator fragmentsIter = ts.fragments().iterator(); > for (Object value : ts.values()) { > sb.append(fragmentsIter.next()); > //------------------------------------------ > if (value instanceof Future future) { > if (future instanceof FutureTask task) { > task.run(); > } > > try { > sb.append(future.get()); > } catch (InterruptedException | ExecutionException e) { > throw new RuntimeException(e); > } > } else if (value instanceof Supplier supplier) { > sb.append(supplier.get()); > } else { > sb.append(value); > } > //------------------------------------------ > } > sb.append(fragmentsIter.next()); > return sb.toString(); > }; > > > To answer Attila's comments. 1) Nothing is computed unless the policy performs an action, so a logging policy > that has an if(ENABLED) around it's main code and static final ENABLED = false; will have little or no overhead. > 2) One of the primary goals of template policies is to screen inputs to prevent injection attacks. So a well written policy should not be > any more susceptible that a println statement. There is no interpretation or a need for interpretation in string templates. Policies may > add interpretation, but then the onus is on the policy > > Cheers, > > -- Jim > > > > On May 7, 2022, at 11:52 AM, Jim Laskey wrote: > > [OOTO] > > I?ve already done examples of all of the items in your list (all good), but i?m glad you brought these topics up for open discussion. I?ll sit back a see what others have to say. > > However, I would like to point out that deferred evaluation requires an agreement from the policy to special case suppliers (or futures). This is something we don?t really want to bake in. Every policy should be free to pick and choose what it does. > > Another approach is to use policy chaining where one policy in the chain evaluates suppliers and futures to produce a new TemplatedString to be processed by the next policy in the chain. > > The ugliness of lambdas in an embedded expression is made worse by the fact it has to be cast to Supplier or Future since the default type for embedded expressions is Object. In all my examples, I?ve out of lined these types of expressions and used a local or better yet a global constant variable (ex., TIME_STAMP) to use in the embedded expression. I think this technique makes the string template read better. > > When I?m back the end of next week I can post some examples. > > Cheers, > > ?Jim > > After this response we should move the discussion to the amber-dev mailing list. > > ?? > > On May 7, 2022, at 10:58 AM, Adam Juraszek wrote: > > ?The TemplatedString proposal is amazing; I can see using it in many places. > I have a few comments that I have not seen addressed. > > Logging > ======= > > One place where some kind of string templating is often used is logging. > Most logging frameworks use a pair of braces {}. TemplatedStrings have the > potential to revolutionize it. > > Log record usually consists of the following parts that the programmer > needs to provide: > * log level (such as error, info) > * message (such as "Oh no, the value {} is negative") > * parameters (such as -42) > * exception/throwable (only sometimes) > > Let's assume that TemplatedString is performant and virtually free, then > these options would be possible. I cannot say which syntax the logging > frameworks would choose: > > logger.error."Oh no, the value \{value} is negative\{ex}"; > logger.error("Oh no, the value \{value} is negative", ex); > logger."Oh no, the value \{value} is negative".error(ex); > > Versus the existing: > > logger.error("Oh no, the value {} is negative", value, ex); > > Lazy Logging > ============ > > Some logging frameworks such as Log4j2 allow lazy evaluation for example: > > logger.trace("Number is {}", () -> getRandomNumber()); > > I must admit that doing the same using TemplatedStrings is ugly and the 6 > distinct characters introducing a lazily evaluated value are impractical: > > logger.trace("Number is \{() -> getRandomNumber()}"); > > Can we offer something better using TemplatedStrings? The JEP already talks > about evaluation. For example, this is valid: > > int index; > String data = STR."\{index=0}, \{index++}"; > > and mentions that the TemplatedString provides a list of values: > > TemplatedString ts = "\{x} plus \{y} equals \{x + y}"; > List values = ts.values(); > > I want to propose an additional syntax. The escape sequence \( would denote > a lazily computed value - probably stored as a Supplier. Because the value > may not be evaluated, it must only refer to effectively final variables > (the same rules as for lambda expressions apply). > > The following should be illegal: > > int index; > String data = STR."\(index=0), \(index++)"; > > The logging use-case could be expressed more naturally: > > logger.trace("Number is \(getRandomNumber())"); > > I personally can see a parallel between \(...) and ()-> as both use > parentheses. This unfortunately is the exact opposite of the syntax of > closures in Groovy, where {123} is "lazy" and (123) produces the value > immediately. Different languages, different choices. > > When should this lazy expression be evaluated, and who is responsible for > it? It can either be: > * when ts.values() is called; > * when the value is accessed (via ts.get(N) or .next() on the List's > Iterator); > * manually when it is consumed by the author of the policy using an > instanceof check for Supplier. > > I keep this as an open question (first we need to find out if this is even > a useful feature). It also remains open what to do when a user provides a > Supplier manually: > > Supplier s = () -> 123 > logger.trace("Number is \{s} \(s)"); > > Another way to express a Supplier is via a method reference, which should > also be supported and would suggest the equivalence of the following: > > logger.trace("Number is \{this::getRandomNumber}"); > logger.trace("Number is \(this.getRandomNumber())"); > > Blocks > ====== > > What if the computation of the value requires multiple statements? > > STR."The intersection of \{a} and \{b} is \{((Supplier) ()-> {var c = new > HashSet<>(a); c.retainAll(b); return c;}).get()}" > STR."The intersection of \{a} and \{b} is \{switch(1){default:var c = new > HashSet<>(a); c.retainAll(b); yield c;}}" > > These are (as far as my imagination goes) the easiest ways to turn a block > into an expression. > > What if we introduced a special syntax for blocks? One option would be > simply doubling the braces: > > STR."The intersection of \{a} and \{b} is \{{var c = new HashSet<>(a); > c.retainAll(b); yield c;}}" > > The last closing brace is probably not needed but keeping them balanced > will help dumb editors with highlighting. > > This could be a more general syntax useful even outside TemplatedStrings > (especially in final field initialization): > > var intersection = {var c = new HashSet<>(a); c.retainAll(b); yield c;}; > > Lazy Blocks > =========== > > We may want to combine the two proposed features and compute the > intersection lazily when logging: > > logger.trace("The intersection of \{a} and \{b} is \({var c = new > HashSet<>(a); c.retainAll(b); return c;})") > > which would be equivalent to this already supported syntax provided that > the policy understands Supplier values: > > logger.trace("The intersection of \{a} and \{b} is \{() -> {var c = new > HashSet<>(a); c.retainAll(b); return c;}}") > > If we introduce \( as described in Lazy Logging above and say that > \(expression) is equivalent to \{() -> expression}, then \({statements}) > would naturally be equivalent to \{() -> {statements}}. > > Literals > ======== > > It was already mentioned in the JEP that it is possible to create a policy > returning a JSON object. We can also think of them as a way to express > literals. Some Java types already have a way to parse a String. Some > examples are: > > DURATION."PT20.345S" > Duration.from("PT20.345S") > > COMPLEX."1+3i" > Complex.from(1, 3) > > US_DATE."7/4/2022" > LocalDate.of(2022, 7, 4) > > This is almost as powerful as what C++ offers ( > https://en.cppreference.com/w/cpp/language/user_literal). > > Conclusion > ========== > > I am very happy with the current proposal and tried to explore some > possible extensions. I proposed a syntax for lazy evaluation of values, > blocks as values, and a combination thereof. I would like to hear from you, > whether this is something worth considering, and whether there is demand > for it. > > Regards > Adam > > From james.laskey at oracle.com Thu May 12 19:18:57 2022 From: james.laskey at oracle.com (Jim Laskey) Date: Thu, 12 May 2022 19:18:57 +0000 Subject: [External] : Re: TemplatedString feedback and extension - logging use-case, lazy values, blocks In-Reply-To: References: <74E94FCF-F3A4-4101-85B0-1A6CB0B6C4DF@oracle.com> Message-ID: I'm afraid you have to build your own git clone https://github.com/openjdk/amber.git cd amber git branch templated-strings then build using regular instructions On May 12, 2022, at 3:30 PM, Attila Kelemen > wrote: Also, btw, is there a reference implementation I can play with? Attila Kelemen > ezt ?rta (id?pont: 2022. m?j. 12., Cs, 20:07): It seems I did too much extrapolation after sloppy reading. Sorry for that. Anyway, this seems nice. Just one thing (which is partially mentioned in the alternatives section of the JEP). The syntax looks a bit bizarre to me (even though it isn't ambiguous), because this adds an additional conceptual weight to the language given that it looks like as if the templated string instance is the method name. In fact, the syntax `MY_POLICY."\{x};\{y}"` could be moved out from this JEP, and simply provide a method both ways: - `TemplatedString.format(TemplatePolicy)` - `TemplatePolicy.apply(TemplatedString)` (already in JEP). If we ever needed the JEP provided sugar, then I would try with something more generic that would benefit other things as well consistently, not just special case string templates. Since as I can see the main benefit of the syntax is that we don't need to bother with parenthesis. But otherwise, this feature would be indeed very useful. Though, I can already see people abusing a TO_STRING policy (which will surely be provided in commons-lang or similar, if not the JDK) :) Attila Jim Laskey > ezt ?rta (id?pont: 2022. m?j. 12., Cs, 19:25): [Note that all code samples below are speculative and subject to change. There is also an assumption reader has read and has an understanding of the JEP draft. https://openjdk.java.net/jeps/8273943] I've had a chance to isolate some examples to demonstrate an approach for deferred evaluation using suppliers. Starting with the general pattern for a (string result) policy as follows; static final StringPolicy MY_SP = ts -> { StringBuilder sb = new StringBuilder(); Iterator fragmentsIter = ts.fragments().iterator(); for (Object value : ts.values()) { sb.append(fragmentsIter.next()); sb.append(value); } sb.append(fragmentsIter.next()); return sb.toString(); }; ... int x = 10, y = 20; String result = MY_SP."\{x} + \{y} = \{x + y}"; // The result will be "10 + 20 = 30". To introduce deferred evaluation we can add a test for java.util.function.Supplier and use the supplier's value instead; static final StringPolicy MY_SP = ts -> { StringBuilder sb = new StringBuilder(); Iterator fragmentsIter = ts.fragments().iterator(); for (Object value : ts.values()) { sb.append(fragmentsIter.next()); //------------------------------------------ if (value instanceof Supplier supplier) { sb.append(supplier.get()); } else { sb.append(value); } //------------------------------------------ } sb.append(fragmentsIter.next()); return sb.toString(); }; ... static final DateTimeFormatter HH_MM_SS = DateTimeFormatter.ofPattern("HH:mm:ss"); static final Supplier TIME_STAMP = () -> LocalDateTime.now().toLocalTime().format(HH_MM_SS); ... String message = MY_SP."\{TIME_STAMP}: Received response"; // The result will be "13:56:48: Received response". In this case, the formatting of the time stamp is deferred until the policy is applied. This example can be expanded to test for java.util.concurrent.Future and java.util.concurrent.FutureTask. The advantage of futures over Supplier is that they are only evaluated once; static final StringPolicy MY_SP = ts -> { StringBuilder sb = new StringBuilder(); Iterator fragmentsIter = ts.fragments().iterator(); for (Object value : ts.values()) { sb.append(fragmentsIter.next()); //------------------------------------------ if (value instanceof Future future) { if (future instanceof FutureTask task) { task.run(); } try { sb.append(future.get()); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } else if (value instanceof Supplier supplier) { sb.append(supplier.get()); } else { sb.append(value); } //------------------------------------------ } sb.append(fragmentsIter.next()); return sb.toString(); }; To answer Attila's comments. 1) Nothing is computed unless the policy performs an action, so a logging policy that has an if(ENABLED) around it's main code and static final ENABLED = false; will have little or no overhead. 2) One of the primary goals of template policies is to screen inputs to prevent injection attacks. So a well written policy should not be any more susceptible that a println statement. There is no interpretation or a need for interpretation in string templates. Policies may add interpretation, but then the onus is on the policy Cheers, -- Jim On May 7, 2022, at 11:52 AM, Jim Laskey > wrote: [OOTO] I?ve already done examples of all of the items in your list (all good), but i?m glad you brought these topics up for open discussion. I?ll sit back a see what others have to say. However, I would like to point out that deferred evaluation requires an agreement from the policy to special case suppliers (or futures). This is something we don?t really want to bake in. Every policy should be free to pick and choose what it does. Another approach is to use policy chaining where one policy in the chain evaluates suppliers and futures to produce a new TemplatedString to be processed by the next policy in the chain. The ugliness of lambdas in an embedded expression is made worse by the fact it has to be cast to Supplier or Future since the default type for embedded expressions is Object. In all my examples, I?ve out of lined these types of expressions and used a local or better yet a global constant variable (ex., TIME_STAMP) to use in the embedded expression. I think this technique makes the string template read better. When I?m back the end of next week I can post some examples. Cheers, ?Jim After this response we should move the discussion to the amber-dev mailing list. ?? On May 7, 2022, at 10:58 AM, Adam Juraszek > wrote: ?The TemplatedString proposal is amazing; I can see using it in many places. I have a few comments that I have not seen addressed. Logging ======= One place where some kind of string templating is often used is logging. Most logging frameworks use a pair of braces {}. TemplatedStrings have the potential to revolutionize it. Log record usually consists of the following parts that the programmer needs to provide: * log level (such as error, info) * message (such as "Oh no, the value {} is negative") * parameters (such as -42) * exception/throwable (only sometimes) Let's assume that TemplatedString is performant and virtually free, then these options would be possible. I cannot say which syntax the logging frameworks would choose: logger.error."Oh no, the value \{value} is negative\{ex}"; logger.error("Oh no, the value \{value} is negative", ex); logger."Oh no, the value \{value} is negative".error(ex); Versus the existing: logger.error("Oh no, the value {} is negative", value, ex); Lazy Logging ============ Some logging frameworks such as Log4j2 allow lazy evaluation for example: logger.trace("Number is {}", () -> getRandomNumber()); I must admit that doing the same using TemplatedStrings is ugly and the 6 distinct characters introducing a lazily evaluated value are impractical: logger.trace("Number is \{() -> getRandomNumber()}"); Can we offer something better using TemplatedStrings? The JEP already talks about evaluation. For example, this is valid: int index; String data = STR."\{index=0}, \{index++}"; and mentions that the TemplatedString provides a list of values: TemplatedString ts = "\{x} plus \{y} equals \{x + y}"; List values = ts.values(); I want to propose an additional syntax. The escape sequence \( would denote a lazily computed value - probably stored as a Supplier. Because the value may not be evaluated, it must only refer to effectively final variables (the same rules as for lambda expressions apply). The following should be illegal: int index; String data = STR."\(index=0), \(index++)"; The logging use-case could be expressed more naturally: logger.trace("Number is \(getRandomNumber())"); I personally can see a parallel between \(...) and ()-> as both use parentheses. This unfortunately is the exact opposite of the syntax of closures in Groovy, where {123} is "lazy" and (123) produces the value immediately. Different languages, different choices. When should this lazy expression be evaluated, and who is responsible for it? It can either be: * when ts.values() is called; * when the value is accessed (via ts.get(N) or .next() on the List's Iterator); * manually when it is consumed by the author of the policy using an instanceof check for Supplier. I keep this as an open question (first we need to find out if this is even a useful feature). It also remains open what to do when a user provides a Supplier manually: Supplier s = () -> 123 logger.trace("Number is \{s} \(s)"); Another way to express a Supplier is via a method reference, which should also be supported and would suggest the equivalence of the following: logger.trace("Number is \{this::getRandomNumber}"); logger.trace("Number is \(this.getRandomNumber())"); Blocks ====== What if the computation of the value requires multiple statements? STR."The intersection of \{a} and \{b} is \{((Supplier) ()-> {var c = new HashSet<>(a); c.retainAll(b); return c;}).get()}" STR."The intersection of \{a} and \{b} is \{switch(1){default:var c = new HashSet<>(a); c.retainAll(b); yield c;}}" These are (as far as my imagination goes) the easiest ways to turn a block into an expression. What if we introduced a special syntax for blocks? One option would be simply doubling the braces: STR."The intersection of \{a} and \{b} is \{{var c = new HashSet<>(a); c.retainAll(b); yield c;}}" The last closing brace is probably not needed but keeping them balanced will help dumb editors with highlighting. This could be a more general syntax useful even outside TemplatedStrings (especially in final field initialization): var intersection = {var c = new HashSet<>(a); c.retainAll(b); yield c;}; Lazy Blocks =========== We may want to combine the two proposed features and compute the intersection lazily when logging: logger.trace("The intersection of \{a} and \{b} is \({var c = new HashSet<>(a); c.retainAll(b); return c;})") which would be equivalent to this already supported syntax provided that the policy understands Supplier values: logger.trace("The intersection of \{a} and \{b} is \{() -> {var c = new HashSet<>(a); c.retainAll(b); return c;}}") If we introduce \( as described in Lazy Logging above and say that \(expression) is equivalent to \{() -> expression}, then \({statements}) would naturally be equivalent to \{() -> {statements}}. Literals ======== It was already mentioned in the JEP that it is possible to create a policy returning a JSON object. We can also think of them as a way to express literals. Some Java types already have a way to parse a String. Some examples are: DURATION."PT20.345S" Duration.from("PT20.345S") COMPLEX."1+3i" Complex.from(1, 3) US_DATE."7/4/2022" LocalDate.of(2022, 7, 4) This is almost as powerful as what C++ offers ( https://urldefense.com/v3/__https://en.cppreference.com/w/cpp/language/user_literal__;!!ACWV5N9M2RV99hQ!OO_WtTpeKG-kPZyVJDePY-5LxA69HkWwiQFa-r4nlO05bDREIFVJ0vJ3v0EdlJzD9jk-t6oVSBZHI8AJlcjbRv9r4eZvpw$ ). Conclusion ========== I am very happy with the current proposal and tried to explore some possible extensions. I proposed a syntax for lazy evaluation of values, blocks as values, and a combination thereof. I would like to hear from you, whether this is something worth considering, and whether there is demand for it. Regards Adam From forax at univ-mlv.fr Fri May 13 09:16:09 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 13 May 2022 11:16:09 +0200 (CEST) Subject: TemplatedString feedback - logging use-case In-Reply-To: <74E94FCF-F3A4-4101-85B0-1A6CB0B6C4DF@oracle.com> References: <74E94FCF-F3A4-4101-85B0-1A6CB0B6C4DF@oracle.com> Message-ID: <1648174243.2739614.1652433369075.JavaMail.zimbra@u-pem.fr> I think the logger is a great example of codes that should work. I think we are well aware of Log4Shell and the danger of dynamic interpolation, by that i mean interpolation that depends on the dynamic class of the value instead of the static type of the value. It's not always possible to only use the static type, if we do not allow java.lang.Object people will not use our new API but at least we can easily distinguish what should be escaped (objects) from what should not. To Adam, the policy is a not the only way to use a templated string, you can write your methods log/warning/info/error etc to take a TemplatedString as parameter. For me, at the moment, the whole API is not in a great shape. TemplatePolicy is more of less restricted to literals because apply() does not take parameters. TemplatedString forget the type of every values and box them, so it can not be really used as parameter of a Logger method because any other existing methods are faster. Also PolicyLinkage should not be public because only the classes of the JDK can use it, the interface is sealed. It's like @SignaturePolymorphic, the compiler needs to know the interface but it's restricted to JDK only usage. I suppose the next iteration will use the carrier API to store the values of the templated string ? So the TemplatedString will store the stencil, a carrier object and the MethodType describing the carrier (it can still expose the values as a List for convenience). With that i suppose we can get good enough performance if the logger methods takes a Supplier as parameter (the lambda will have to capture the values of the templated string so by introspection at runtime you can observe the declared type of those values and you only need to do that once per call). But it does not solve the issue of the missing types at compile time, i don't think people will find nice to have to declare the lambda in a static field each time they want to pass one as value of a templated string like below. regards, R?mi ----- Original Message ----- > From: "Jim Laskey" > To: "amber-dev" , "Adam Juraszek" > Cc: "attila kelemen85" > Sent: Thursday, May 12, 2022 7:25:02 PM > Subject: Re: TemplatedString feedback and extension - logging use-case, lazy values, blocks > [Note that all code samples below are speculative and subject to change. There > is also an assumption reader has read and has an understanding of the JEP > draft. https://openjdk.java.net/jeps/8273943] > > I've had a chance to isolate some examples to demonstrate an approach for > deferred evaluation using suppliers. > > Starting with the general pattern for a (string result) policy as follows; > > > static final StringPolicy MY_SP = ts -> { > StringBuilder sb = new StringBuilder(); > Iterator fragmentsIter = ts.fragments().iterator(); > for (Object value : ts.values()) { > sb.append(fragmentsIter.next()); > sb.append(value); > } > sb.append(fragmentsIter.next()); > return sb.toString(); > }; > > ... > > int x = 10, y = 20; > String result = MY_SP."\{x} + \{y} = \{x + y}"; // The result will be "10 + 20 = > 30". > > To introduce deferred evaluation we can add a test for > java.util.function.Supplier and > use the supplier's value instead; > > static final StringPolicy MY_SP = ts -> { > StringBuilder sb = new StringBuilder(); > Iterator fragmentsIter = ts.fragments().iterator(); > for (Object value : ts.values()) { > sb.append(fragmentsIter.next()); > //------------------------------------------ > if (value instanceof Supplier supplier) { > sb.append(supplier.get()); > } else { > sb.append(value); > } > //------------------------------------------ > } > sb.append(fragmentsIter.next()); > return sb.toString(); > }; > > ... > > static final DateTimeFormatter HH_MM_SS = > DateTimeFormatter.ofPattern("HH:mm:ss"); > static final Supplier TIME_STAMP = () -> > LocalDateTime.now().toLocalTime().format(HH_MM_SS); > > ... > > > String message = MY_SP."\{TIME_STAMP}: Received response"; // The result will be > "13:56:48: Received response". > > In this case, the formatting of the time stamp is deferred until the policy is > applied. This example can be expanded to test for > java.util.concurrent.Future and java.util.concurrent.FutureTask. The advantage > of futures > over Supplier is that they are only evaluated once; > > static final StringPolicy MY_SP = ts -> { > StringBuilder sb = new StringBuilder(); > Iterator fragmentsIter = ts.fragments().iterator(); > for (Object value : ts.values()) { > sb.append(fragmentsIter.next()); > //------------------------------------------ > if (value instanceof Future future) { > if (future instanceof FutureTask task) { > task.run(); > } > > try { > sb.append(future.get()); > } catch (InterruptedException | ExecutionException e) { > throw new RuntimeException(e); > } > } else if (value instanceof Supplier supplier) { > sb.append(supplier.get()); > } else { > sb.append(value); > } > //------------------------------------------ > } > sb.append(fragmentsIter.next()); > return sb.toString(); > }; > > To answer Attila's comments. 1) Nothing is computed unless the policy performs > an action, so a logging policy > that has an if(ENABLED) around it's main code and static final ENABLED = false; > will have little or no overhead. > 2) One of the primary goals of template policies is to screen inputs to prevent > injection attacks. So a well written policy should not be > any more susceptible that a println statement. There is no interpretation or a > need for interpretation in string templates. Policies may > add interpretation, but then the onus is on the policy > > Cheers, > > -- Jim > > > On May 7, 2022, at 11:52 AM, Jim Laskey > > wrote: > > [OOTO] > > I?ve already done examples of all of the items in your list (all good), but i?m > glad you brought these topics up for open discussion. I?ll sit back a see what > others have to say. > > However, I would like to point out that deferred evaluation requires an > agreement from the policy to special case suppliers (or futures). This is > something we don?t really want to bake in. Every policy should be free to pick > and choose what it does. > > Another approach is to use policy chaining where one policy in the chain > evaluates suppliers and futures to produce a new TemplatedString to be > processed by the next policy in the chain. > > The ugliness of lambdas in an embedded expression is made worse by the fact it > has to be cast to Supplier or Future since the default type for embedded > expressions is Object. In all my examples, I?ve out of lined these types of > expressions and used a local or better yet a global constant variable (ex., > TIME_STAMP) to use in the embedded expression. I think this technique makes the > string template read better. > > When I?m back the end of next week I can post some examples. > > Cheers, > > ?Jim > > After this response we should move the discussion to the amber-dev mailing list. > > ?? > > On May 7, 2022, at 10:58 AM, Adam Juraszek > > wrote: > >?The TemplatedString proposal is amazing; I can see using it in many places. > I have a few comments that I have not seen addressed. > > Logging > ======= > > One place where some kind of string templating is often used is logging. > Most logging frameworks use a pair of braces {}. TemplatedStrings have the > potential to revolutionize it. > > Log record usually consists of the following parts that the programmer > needs to provide: > * log level (such as error, info) > * message (such as "Oh no, the value {} is negative") > * parameters (such as -42) > * exception/throwable (only sometimes) > > Let's assume that TemplatedString is performant and virtually free, then > these options would be possible. I cannot say which syntax the logging > frameworks would choose: > > logger.error."Oh no, the value \{value} is negative\{ex}"; > logger.error("Oh no, the value \{value} is negative", ex); > logger."Oh no, the value \{value} is negative".error(ex); > > Versus the existing: > > logger.error("Oh no, the value {} is negative", value, ex); > > Lazy Logging > ============ > > Some logging frameworks such as Log4j2 allow lazy evaluation for example: > > logger.trace("Number is {}", () -> getRandomNumber()); > > I must admit that doing the same using TemplatedStrings is ugly and the 6 > distinct characters introducing a lazily evaluated value are impractical: > > logger.trace("Number is \{() -> getRandomNumber()}"); > > Can we offer something better using TemplatedStrings? The JEP already talks > about evaluation. For example, this is valid: > > int index; > String data = STR."\{index=0}, \{index++}"; > > and mentions that the TemplatedString provides a list of values: > > TemplatedString ts = "\{x} plus \{y} equals \{x + y}"; > List values = ts.values(); > > I want to propose an additional syntax. The escape sequence \( would denote > a lazily computed value - probably stored as a Supplier. Because the value > may not be evaluated, it must only refer to effectively final variables > (the same rules as for lambda expressions apply). > > The following should be illegal: > > int index; > String data = STR."\(index=0), \(index++)"; > > The logging use-case could be expressed more naturally: > > logger.trace("Number is \(getRandomNumber())"); > > I personally can see a parallel between \(...) and ()-> as both use > parentheses. This unfortunately is the exact opposite of the syntax of > closures in Groovy, where {123} is "lazy" and (123) produces the value > immediately. Different languages, different choices. > > When should this lazy expression be evaluated, and who is responsible for > it? It can either be: > * when ts.values() is called; > * when the value is accessed (via ts.get(N) or .next() on the List's > Iterator); > * manually when it is consumed by the author of the policy using an > instanceof check for Supplier. > > I keep this as an open question (first we need to find out if this is even > a useful feature). It also remains open what to do when a user provides a > Supplier manually: > > Supplier s = () -> 123 > logger.trace("Number is \{s} \(s)"); > > Another way to express a Supplier is via a method reference, which should > also be supported and would suggest the equivalence of the following: > > logger.trace("Number is \{this::getRandomNumber}"); > logger.trace("Number is \(this.getRandomNumber())"); > > Blocks > ====== > > What if the computation of the value requires multiple statements? > > STR."The intersection of \{a} and \{b} is \{((Supplier) ()-> {var c = new > HashSet<>(a); c.retainAll(b); return c;}).get()}" > STR."The intersection of \{a} and \{b} is \{switch(1){default:var c = new > HashSet<>(a); c.retainAll(b); yield c;}}" > > These are (as far as my imagination goes) the easiest ways to turn a block > into an expression. > > What if we introduced a special syntax for blocks? One option would be > simply doubling the braces: > > STR."The intersection of \{a} and \{b} is \{{var c = new HashSet<>(a); > c.retainAll(b); yield c;}}" > > The last closing brace is probably not needed but keeping them balanced > will help dumb editors with highlighting. > > This could be a more general syntax useful even outside TemplatedStrings > (especially in final field initialization): > > var intersection = {var c = new HashSet<>(a); c.retainAll(b); yield c;}; > > Lazy Blocks > =========== > > We may want to combine the two proposed features and compute the > intersection lazily when logging: > > logger.trace("The intersection of \{a} and \{b} is \({var c = new > HashSet<>(a); c.retainAll(b); return c;})") > > which would be equivalent to this already supported syntax provided that > the policy understands Supplier values: > > logger.trace("The intersection of \{a} and \{b} is \{() -> {var c = new > HashSet<>(a); c.retainAll(b); return c;}}") > > If we introduce \( as described in Lazy Logging above and say that > \(expression) is equivalent to \{() -> expression}, then \({statements}) > would naturally be equivalent to \{() -> {statements}}. > > Literals > ======== > > It was already mentioned in the JEP that it is possible to create a policy > returning a JSON object. We can also think of them as a way to express > literals. Some Java types already have a way to parse a String. Some > examples are: > > DURATION."PT20.345S" > Duration.from("PT20.345S") > > COMPLEX."1+3i" > Complex.from(1, 3) > > US_DATE."7/4/2022" > LocalDate.of(2022, 7, 4) > > This is almost as powerful as what C++ offers ( > https://en.cppreference.com/w/cpp/language/user_literal). > > Conclusion > ========== > > I am very happy with the current proposal and tried to explore some > possible extensions. I proposed a syntax for lazy evaluation of values, > blocks as values, and a combination thereof. I would like to hear from you, > whether this is something worth considering, and whether there is demand > for it. > > Regards > Adam From tanksherman27 at gmail.com Fri May 13 11:45:59 2022 From: tanksherman27 at gmail.com (Julian Waters) Date: Fri, 13 May 2022 19:45:59 +0800 Subject: Discussion on boilerplate for code tightly bound to OOP In-Reply-To: References: <369d5861-df6c-9768-d764-4ad67f2babfb@oracle.com> Message-ID: I guess that makes sense depending on one's definition of the pattern, I was just wondering out loud whether or not using enums with only one instance defined compared to regular "POJO" classes for singletons in a codebase is currently considered good practice within the ecosystem best regards, Julian On Thu, May 12, 2022 at 11:41 PM Kevin Bourrillion wrote: > Tangent > > On Thu, May 12, 2022 at 6:16 AM Julian Waters > wrote: > > Granted, it would be interesting to >> see them being added as a full-on language feature though (I'm unsure if >> people using enums as singletons nowadays is encouraged or not). >> > > (Is there controversy here? What is a singleton if not an enumerated type > where N == 1? I don't think we're using enums "as" singletons, they just > *are* singletons until you add a second constant.) > > -- > Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com > From james.laskey at oracle.com Fri May 13 13:39:48 2022 From: james.laskey at oracle.com (Jim Laskey) Date: Fri, 13 May 2022 13:39:48 +0000 Subject: [External] : TemplatedString feedback - logging use-case In-Reply-To: <1648174243.2739614.1652433369075.JavaMail.zimbra@u-pem.fr> References: <74E94FCF-F3A4-4101-85B0-1A6CB0B6C4DF@oracle.com> <1648174243.2739614.1652433369075.JavaMail.zimbra@u-pem.fr> Message-ID: <0E5588F4-4F77-43B4-829C-89E1663BE3B9@oracle.com> As a follow up for the examples I gave yesterday, I forgot I mentioned 'chaining' in my first reply. static final SimplePolicy EVAL = ts -> { List values = ts.values() .stream() .map(v -> v instanceof Supplier s ? s.get() : v) .toList(); return TemplatedString.of(ts.stencil(), values); }; ... static final StringPolicy STR_EVAL = StringPolicy.chain(STR, EVAL); ... String deferredResult = STR_EVAL."\{supplier}"; In this example, the EVAL policy simply 'gets' the value for each Supplier value. The end result is a new TemplatedString with all of its values fully resolved. By using the chain method, we can create a new policy that combines resolution of the EVAL policy with concatenation done by the STR policy. The above deferredResult expression is equivalent to; String deferredResult = STR.apply(EVAL.apply("\{supplier}")); Cheers, -- Jim On May 13, 2022, at 6:16 AM, Remi Forax > wrote: I think the logger is a great example of codes that should work. I think we are well aware of Log4Shell and the danger of dynamic interpolation, by that i mean interpolation that depends on the dynamic class of the value instead of the static type of the value. It's not always possible to only use the static type, if we do not allow java.lang.Object people will not use our new API but at least we can easily distinguish what should be escaped (objects) from what should not. To Adam, the policy is a not the only way to use a templated string, you can write your methods log/warning/info/error etc to take a TemplatedString as parameter. For me, at the moment, the whole API is not in a great shape. TemplatePolicy is more of less restricted to literals because apply() does not take parameters. TemplatedString forget the type of every values and box them, so it can not be really used as parameter of a Logger method because any other existing methods are faster. Also PolicyLinkage should not be public because only the classes of the JDK can use it, the interface is sealed. It's like @SignaturePolymorphic, the compiler needs to know the interface but it's restricted to JDK only usage. I suppose the next iteration will use the carrier API to store the values of the templated string ? So the TemplatedString will store the stencil, a carrier object and the MethodType describing the carrier (it can still expose the values as a List for convenience). With that i suppose we can get good enough performance if the logger methods takes a Supplier as parameter (the lambda will have to capture the values of the templated string so by introspection at runtime you can observe the declared type of those values and you only need to do that once per call). But it does not solve the issue of the missing types at compile time, i don't think people will find nice to have to declare the lambda in a static field each time they want to pass one as value of a templated string like below. regards, R?mi ----- Original Message ----- From: "Jim Laskey" > To: "amber-dev" >, "Adam Juraszek" > Cc: "attila kelemen85" > Sent: Thursday, May 12, 2022 7:25:02 PM Subject: Re: TemplatedString feedback and extension - logging use-case, lazy values, blocks [Note that all code samples below are speculative and subject to change. There is also an assumption reader has read and has an understanding of the JEP draft. https://openjdk.java.net/jeps/8273943] I've had a chance to isolate some examples to demonstrate an approach for deferred evaluation using suppliers. Starting with the general pattern for a (string result) policy as follows; static final StringPolicy MY_SP = ts -> { StringBuilder sb = new StringBuilder(); Iterator fragmentsIter = ts.fragments().iterator(); for (Object value : ts.values()) { sb.append(fragmentsIter.next()); sb.append(value); } sb.append(fragmentsIter.next()); return sb.toString(); }; ... int x = 10, y = 20; String result = MY_SP."\{x} + \{y} = \{x + y}"; // The result will be "10 + 20 = 30". To introduce deferred evaluation we can add a test for java.util.function.Supplier and use the supplier's value instead; static final StringPolicy MY_SP = ts -> { StringBuilder sb = new StringBuilder(); Iterator fragmentsIter = ts.fragments().iterator(); for (Object value : ts.values()) { sb.append(fragmentsIter.next()); //------------------------------------------ if (value instanceof Supplier supplier) { sb.append(supplier.get()); } else { sb.append(value); } //------------------------------------------ } sb.append(fragmentsIter.next()); return sb.toString(); }; ... static final DateTimeFormatter HH_MM_SS = DateTimeFormatter.ofPattern("HH:mm:ss"); static final Supplier TIME_STAMP = () -> LocalDateTime.now().toLocalTime().format(HH_MM_SS); ... String message = MY_SP."\{TIME_STAMP}: Received response"; // The result will be "13:56:48: Received response". In this case, the formatting of the time stamp is deferred until the policy is applied. This example can be expanded to test for java.util.concurrent.Future and java.util.concurrent.FutureTask. The advantage of futures over Supplier is that they are only evaluated once; static final StringPolicy MY_SP = ts -> { StringBuilder sb = new StringBuilder(); Iterator fragmentsIter = ts.fragments().iterator(); for (Object value : ts.values()) { sb.append(fragmentsIter.next()); //------------------------------------------ if (value instanceof Future future) { if (future instanceof FutureTask task) { task.run(); } try { sb.append(future.get()); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } else if (value instanceof Supplier supplier) { sb.append(supplier.get()); } else { sb.append(value); } //------------------------------------------ } sb.append(fragmentsIter.next()); return sb.toString(); }; To answer Attila's comments. 1) Nothing is computed unless the policy performs an action, so a logging policy that has an if(ENABLED) around it's main code and static final ENABLED = false; will have little or no overhead. 2) One of the primary goals of template policies is to screen inputs to prevent injection attacks. So a well written policy should not be any more susceptible that a println statement. There is no interpretation or a need for interpretation in string templates. Policies may add interpretation, but then the onus is on the policy Cheers, -- Jim On May 7, 2022, at 11:52 AM, Jim Laskey > wrote: [OOTO] I?ve already done examples of all of the items in your list (all good), but i?m glad you brought these topics up for open discussion. I?ll sit back a see what others have to say. However, I would like to point out that deferred evaluation requires an agreement from the policy to special case suppliers (or futures). This is something we don?t really want to bake in. Every policy should be free to pick and choose what it does. Another approach is to use policy chaining where one policy in the chain evaluates suppliers and futures to produce a new TemplatedString to be processed by the next policy in the chain. The ugliness of lambdas in an embedded expression is made worse by the fact it has to be cast to Supplier or Future since the default type for embedded expressions is Object. In all my examples, I?ve out of lined these types of expressions and used a local or better yet a global constant variable (ex., TIME_STAMP) to use in the embedded expression. I think this technique makes the string template read better. When I?m back the end of next week I can post some examples. Cheers, ?Jim After this response we should move the discussion to the amber-dev mailing list. ?? On May 7, 2022, at 10:58 AM, Adam Juraszek > wrote: ?The TemplatedString proposal is amazing; I can see using it in many places. I have a few comments that I have not seen addressed. Logging ======= One place where some kind of string templating is often used is logging. Most logging frameworks use a pair of braces {}. TemplatedStrings have the potential to revolutionize it. Log record usually consists of the following parts that the programmer needs to provide: * log level (such as error, info) * message (such as "Oh no, the value {} is negative") * parameters (such as -42) * exception/throwable (only sometimes) Let's assume that TemplatedString is performant and virtually free, then these options would be possible. I cannot say which syntax the logging frameworks would choose: logger.error."Oh no, the value \{value} is negative\{ex}"; logger.error("Oh no, the value \{value} is negative", ex); logger."Oh no, the value \{value} is negative".error(ex); Versus the existing: logger.error("Oh no, the value {} is negative", value, ex); Lazy Logging ============ Some logging frameworks such as Log4j2 allow lazy evaluation for example: logger.trace("Number is {}", () -> getRandomNumber()); I must admit that doing the same using TemplatedStrings is ugly and the 6 distinct characters introducing a lazily evaluated value are impractical: logger.trace("Number is \{() -> getRandomNumber()}"); Can we offer something better using TemplatedStrings? The JEP already talks about evaluation. For example, this is valid: int index; String data = STR."\{index=0}, \{index++}"; and mentions that the TemplatedString provides a list of values: TemplatedString ts = "\{x} plus \{y} equals \{x + y}"; List values = ts.values(); I want to propose an additional syntax. The escape sequence \( would denote a lazily computed value - probably stored as a Supplier. Because the value may not be evaluated, it must only refer to effectively final variables (the same rules as for lambda expressions apply). The following should be illegal: int index; String data = STR."\(index=0), \(index++)"; The logging use-case could be expressed more naturally: logger.trace("Number is \(getRandomNumber())"); I personally can see a parallel between \(...) and ()-> as both use parentheses. This unfortunately is the exact opposite of the syntax of closures in Groovy, where {123} is "lazy" and (123) produces the value immediately. Different languages, different choices. When should this lazy expression be evaluated, and who is responsible for it? It can either be: * when ts.values() is called; * when the value is accessed (via ts.get(N) or .next() on the List's Iterator); * manually when it is consumed by the author of the policy using an instanceof check for Supplier. I keep this as an open question (first we need to find out if this is even a useful feature). It also remains open what to do when a user provides a Supplier manually: Supplier s = () -> 123 logger.trace("Number is \{s} \(s)"); Another way to express a Supplier is via a method reference, which should also be supported and would suggest the equivalence of the following: logger.trace("Number is \{this::getRandomNumber}"); logger.trace("Number is \(this.getRandomNumber())"); Blocks ====== What if the computation of the value requires multiple statements? STR."The intersection of \{a} and \{b} is \{((Supplier) ()-> {var c = new HashSet<>(a); c.retainAll(b); return c;}).get()}" STR."The intersection of \{a} and \{b} is \{switch(1){default:var c = new HashSet<>(a); c.retainAll(b); yield c;}}" These are (as far as my imagination goes) the easiest ways to turn a block into an expression. What if we introduced a special syntax for blocks? One option would be simply doubling the braces: STR."The intersection of \{a} and \{b} is \{{var c = new HashSet<>(a); c.retainAll(b); yield c;}}" The last closing brace is probably not needed but keeping them balanced will help dumb editors with highlighting. This could be a more general syntax useful even outside TemplatedStrings (especially in final field initialization): var intersection = {var c = new HashSet<>(a); c.retainAll(b); yield c;}; Lazy Blocks =========== We may want to combine the two proposed features and compute the intersection lazily when logging: logger.trace("The intersection of \{a} and \{b} is \({var c = new HashSet<>(a); c.retainAll(b); return c;})") which would be equivalent to this already supported syntax provided that the policy understands Supplier values: logger.trace("The intersection of \{a} and \{b} is \{() -> {var c = new HashSet<>(a); c.retainAll(b); return c;}}") If we introduce \( as described in Lazy Logging above and say that \(expression) is equivalent to \{() -> expression}, then \({statements}) would naturally be equivalent to \{() -> {statements}}. Literals ======== It was already mentioned in the JEP that it is possible to create a policy returning a JSON object. We can also think of them as a way to express literals. Some Java types already have a way to parse a String. Some examples are: DURATION."PT20.345S" Duration.from("PT20.345S") COMPLEX."1+3i" Complex.from(1, 3) US_DATE."7/4/2022" LocalDate.of(2022, 7, 4) This is almost as powerful as what C++ offers ( https://urldefense.com/v3/__https://en.cppreference.com/w/cpp/language/user_literal__;!!ACWV5N9M2RV99hQ!M4Ng7vA4iXTBrb9WEbUiISXGems-vPKZjn_H7xYL5Bbhry1gPTadmf4z-QnZ-RhLk6gq_ZmX8GzTDU3t_j0L$ ). Conclusion ========== I am very happy with the current proposal and tried to explore some possible extensions. I proposed a syntax for lazy evaluation of values, blocks as values, and a combination thereof. I would like to hear from you, whether this is something worth considering, and whether there is demand for it. Regards Adam From alautiero at gmail.com Sun May 15 17:59:36 2022 From: alautiero at gmail.com (Alessandro Autiero) Date: Sun, 15 May 2022 19:59:36 +0200 Subject: PROPOSAL: Null safe type system Message-ID: Null safe type system Hello, this is my first time writing a proposal for OpenJDK, but I hope that it will meet the quality standards. I've looked through the discussions in the OpenJDK mailing lists, but I couldn't find any regarding this issue, so I thought I'd propose something on my own. I discussed this proposal previously on the java subreddit here to collect ideas to make this proposal as comprehensive as possible. I apologize in advance for any mistakes. **OVERVIEW** The Java Type System is made up of two different types: primitives and objects. While the first cannot be assigned to the special type null, the latter can. If an expression, for example, a method invocation, takes as an argument a null reference a NullPointerException is thrown. This design choice introduces the need to check for null values to make sure that this doesn't happen. The most common values that are checked are method parameters, especially if the method is exposed publicly, and ones returned by method invocations. To make the latter easier, in Java 8 the java.util.Optional wrapper class was introduced. Being a class, Optional is not suitable as an equivalent to, for example, Swift's Optional which is used often as a method parameter. Despite this obvious design choice, some very popular libraries in the Java Ecosystem, most notably Selenium in my experience, have chosen to use Optional in the context just described. This indicates firmly that there is a need for better handling of null in Java's type system. As a matter of fact, annotations have become a sort of fix for this issue(see the Checker Framework for example), though, in my opinion, this issue should be resolved in the type system itself. What I'd like to propose is to improve Java's type system by leveraging the Optional wrapper, the module system introduced in Java 9(Jigsaw) and the recent developments over at Project Valhalla to better handle the special type null. Considering that our neighbour in the JVM universe, Kotlin, has been designed with a type system that takes into account exactly this issue, I think that it's appropriate to discuss JetBrain's approach. Kotlin's type system, differently from Java's, is made up only of objects. While non-null scalar types, such as Int, use JVM primitive types as an implementation detail, they are not part of the language. Object types can be nullable(the name of the type is followed by a question mark), non-null(the name of the type) or platform types(the name of the type is followed by an exclamation mark) to provide a compatibility layer with Java's type system. In the latter case, the programmer should explicitly declare the type as nullable or non-null(it's not technically needed as the compiler will issue a warning and not an error, but it's advised), while in the others can be inferred. In practice, this means that, for example, the type Int and Int? are two completely different types for the compiler. While this approach can be appropriate when designing a new language, like in Kotlin's case, it certainly isn't feasible in Java's case considering its twenty years of legacy. Now let's look at Swift's approach instead. Swift's type system allows for nullable and non-null object values and follows the same naming conventions as Kotlin, except platform types aren't a thing here. Furthermore, nullable types aren't compiler magic in this case: they are an alias for the Optional enum. For example, the type Int? and Optional are the same type. This also means that declaring, for example, a variable with type Int?? is legal, as this simply translates to Optional>. This approach is better suited for Java as we already have a very similar class that we can leverage: java.util.Optional. Implementing the same concept in Java wouldn't have been previously possible because the Optional wrapper, being a class, is too slow. Thanks to Project Valhalla, though, the Optional wrapper can be transformed in a primitive class. Non-null values assigned to Optional types should be autoboxed by using Optional#of(T), nullable values should be autoboxed by using Optional#ofNullable(T) while the null literal should be an alias of Optional#empty(). This can either be done with some compiler magic, like we currently do with primitive wrappers, or by introducing a full-fledged feature to support boxing and unboxing for classes, but this conversation is not relevant here I think. This means that the performance hit should no longer be a concern. NOTE: An instance of a primitive class doesn't allow for null initializers, though, considering the previously described boxing and unboxing mechanism this should be syntactically legal. If this approach introduced ambiguity, though, a better approach would be to use a value class instead. **CHALLENGES** As previously mentioned, Java's legacy makes implementing such a feature quite hard when considering backwards compatibility. The first step is obviously to make Optional into a value class and this is possible without breaking anything, as far as I know. Furthermore, it's also necessary to make sure that a developer using a Java version with this new hypothetical type system can, for example, use a library built using a version of Java that doesn't. This can be technically done by treating older types as equivalent to platform types and allowing javac to place null checks(NULLCHK operator) when needed. While platform types as a concept aren't strictly necessary, I think that having older types be nullable by default is not the right approach as if the programmer wants to declare the variable as non-null requires implicit conversion(from ExampleType? to ExampleType), which goes against the purpose of this type system, or a manual cast which can be perceived as very verbose and redundant. This challenge was already faced by Google when they transitioned to Dart 2.12 which introduced the concept of nullable and non-null types. Finally, the only challenge would be to make migrating a project from an older Java version as painless as possible. For this new type system to work effectively, types should be non-null by default. Though, Java's current type system has object types nullable by default. This means that if a developer were to transition to this new hypothetical version, everything in the codebase would break. To face this issue, I think that introducing a flag, for example, sound(sound null safety), in the module-info, just like open, is the best approach. The default flag would be, for example, unsound, so that backwards compatibility is preserved even if a module-info is already present. Some may argue though that migrating a whole module to sound null safety is unlikely in major, especially enterprise, applications all at once. This is the reason why we might consider having this flag also on a package level by using the package info. An idea would be to have the flag in front of the package declaration, though there are currently no flags available for packages in package info, having different type systems in a single module is not optimal for a developer working on a codebase and there should also be a hierarchical resolution of flags(for example a package is unsound but its parent module is sound, what do we do?). Apart from these implementation details, this approach ensures backwards compatibility and an easy transition for developers to newer versions. **COMPLEMENTARY OPERATORS** To support such a type system, at least the null-safe de-referencing operator(?.) would need to be introduced. This should not break any existing application because the question mark is not a legal character for variables' names, so no ambiguities can occur. Other useful operators would be the Elvis operator(?:) which is a binary operator that returns the left operand if it's not null otherwise the right one and the double bang operator which is a unary operator that throws a NullPointerException if its operand is null. These aren't strictly needed, but they certainly improve the developer experience. **COMPLEMENTARY KEYWORDS** Introducing an equivalent to the var keyword, introduced in Java 10, for nullable types could be appropriate. Let's say that the initializer of a variable is a literal, but the developer later wants to assign a new value to this variable: how should the compiler know if the type to infer is ExampleType or ExampleType?. The var? keyword would only allow for nullable types, while the var keyword for both nullable and non-nullable. This, though, should probably be discussed. **EXAMPLES** Module Flags: module com.example.project { // The module has unsound null safety } sound module com.example.project { // The module has sound null safety } unsound module com.example.project { // Unsound is redundant, the module has unsound null safety } Example interaction between legacy code and modern code: public void someMethod(){ String? someValue = OldAPI.getSomeValue(); // The return type of getSomeValue is specified by the programmer to be nullable, no checks needed String someOtherValue = OldAPI.getSomeOtherValue(); // The return type of getSomeValue is specified by the programmer to be non-null, the compiler should include a NULLCHK var inferredValue = OldAPI.getSomeValue(); // The return type of getSomeValue is inferred to be nullable by var considering it's a platform type } **CONCLUSION** Discussing the technical changes needed for javac and for the JLS is necessary, though this proposal will probably need a lot of discussion and opinions before we get to this stage in my opinion. The effort would probably be considerable though in the case of the compiler. From numeralnathan at gmail.com Sun May 15 18:10:20 2022 From: numeralnathan at gmail.com (Nathan Reynolds) Date: Sun, 15 May 2022 12:10:20 -0600 Subject: PROPOSAL: Null safe type system In-Reply-To: References: Message-ID: > An idea would be to have the flag in front of the package declaration, though there are currently no flags available for packages in package info... Eclipse null checking framework allows the programmer to put @NonNullByDefault in front of the package declaration in package-info.java. Why not create a JDK annotation instead of a flag? This will be very compatible. On Sun, May 15, 2022 at 12:00 PM Alessandro Autiero wrote: > Null safe type system > > Hello, this is my first time writing a proposal for OpenJDK, but I hope > that it will meet the quality standards. > I've looked through the discussions in the OpenJDK mailing lists, but I > couldn't find any regarding this issue, so I thought I'd propose something > on my own. > I discussed this proposal previously on the java subreddit here > < > https://www.reddit.com/r/java/comments/tiw76i/backwards_compatible_null_safe_nonnull_by_default/ > >to > collect ideas to make this proposal as comprehensive as possible. > I apologize in advance for any mistakes. > > **OVERVIEW** > > The Java Type System is made up of two different types: primitives and > objects. > While the first cannot be assigned to the special type null, the latter > can. > If an expression, for example, a method invocation, takes as an argument a > null reference a NullPointerException is thrown. > This design choice introduces the need to check for null values to make > sure that this doesn't happen. > The most common values that are checked are method parameters, especially > if the method is exposed publicly, and ones returned by method invocations. > To make the latter easier, in Java 8 the java.util.Optional wrapper class > was introduced. > Being a class, Optional is not suitable as an equivalent to, for example, > Swift's Optional which is used often as a method parameter. > Despite this obvious design choice, some very popular libraries in the Java > Ecosystem, most notably Selenium in my experience, have chosen to use > Optional in the context just described. > This indicates firmly that there is a need for better handling of null in > Java's type system. > As a matter of fact, annotations have become a sort of fix for this > issue(see the Checker Framework for example), though, in my opinion, this > issue should be resolved in the type system itself. > What I'd like to propose is to improve Java's type system by leveraging the > Optional wrapper, the module system introduced in Java 9(Jigsaw) and the > recent developments over at Project Valhalla to better handle the special > type null. > Considering that our neighbour in the JVM universe, Kotlin, has been > designed with a type system that takes into account exactly this issue, I > think that it's appropriate to discuss JetBrain's approach. > Kotlin's type system, differently from Java's, is made up only of objects. > While non-null scalar types, such as Int, use JVM primitive types as an > implementation detail, they are not part of the language. > Object types can be nullable(the name of the type is followed by a question > mark), non-null(the name of the type) or platform types(the name of the > type is followed by an exclamation mark) to provide a compatibility layer > with Java's type system. > In the latter case, the programmer should explicitly declare the type as > nullable or non-null(it's not technically needed as the compiler will issue > a warning and not an error, but it's advised), while in the others can be > inferred. > In practice, this means that, for example, the type Int and Int? are two > completely different types for the compiler. > While this approach can be appropriate when designing a new language, like > in Kotlin's case, it certainly isn't feasible in Java's case considering > its twenty years of legacy. > Now let's look at Swift's approach instead. Swift's type system allows for > nullable and non-null object values and follows the same naming conventions > as Kotlin, except platform types aren't a thing here. > Furthermore, nullable types aren't compiler magic in this case: they are an > alias for the Optional enum. > For example, the type Int? and Optional are the same type. > This also means that declaring, for example, a variable with type Int?? is > legal, as this simply translates to Optional>. > This approach is better suited for Java as we already have a very similar > class that we can leverage: java.util.Optional. > Implementing the same concept in Java wouldn't have been previously > possible because the Optional wrapper, being a class, is too slow. > Thanks to Project Valhalla, though, the Optional wrapper can be transformed > in a primitive class. > Non-null values assigned to Optional types should be autoboxed by using > Optional#of(T), nullable values should be autoboxed by using > Optional#ofNullable(T) while the null literal should be an alias of > Optional#empty(). > This can either be done with some compiler magic, like we currently do with > primitive wrappers, or by introducing a full-fledged feature to support > boxing and unboxing for classes, but this conversation is not relevant here > I think. > This means that the performance hit should no longer be a concern. > NOTE: An instance of a primitive class doesn't allow for null initializers, > though, considering the previously described boxing and unboxing mechanism > this should be syntactically legal. If this approach introduced ambiguity, > though, a better approach would be to use a value class instead. > > **CHALLENGES** > > As previously mentioned, Java's legacy makes implementing such a feature > quite hard when considering backwards compatibility. > The first step is obviously to make Optional into a value class and this is > possible without breaking anything, as far as I know. > Furthermore, it's also necessary to make sure that a developer using a Java > version with this new hypothetical type system can, for example, use a > library built using a version of Java that doesn't. > This can be technically done by treating older types as equivalent to > platform types and allowing javac to place null checks(NULLCHK operator) > when needed. > While platform types as a concept aren't strictly necessary, I think that > having older types be nullable by default is not the right approach as if > the programmer wants to declare the variable as non-null requires implicit > conversion(from ExampleType? to ExampleType), which goes against the > purpose of this type system, or a manual cast which can be perceived as > very verbose and redundant. > This challenge was already faced by Google when they transitioned to Dart > 2.12 which introduced the concept of nullable and non-null types. > Finally, the only challenge would be to make migrating a project from an > older Java version as painless as possible. > For this new type system to work effectively, types should be non-null by > default. > Though, Java's current type system has object types nullable by default. > This means that if a developer were to transition to this new hypothetical > version, everything in the codebase would break. > To face this issue, I think that introducing a flag, for example, > sound(sound null safety), in the module-info, just like open, is the best > approach. > The default flag would be, for example, unsound, so that backwards > compatibility is preserved even if a module-info is already present. > Some may argue though that migrating a whole module to sound null safety is > unlikely in major, especially enterprise, applications all at once. > This is the reason why we might consider having this flag also on a package > level by using the package info. > An idea would be to have the flag in front of the package declaration, > though there are currently no flags available for packages in package info, > having different type systems in a single module is not optimal for a > developer working on a codebase and there should also be a hierarchical > resolution of flags(for example a package is unsound but its parent module > is sound, what do we do?). > Apart from these implementation details, this approach ensures backwards > compatibility and an easy transition for developers to newer versions. > > **COMPLEMENTARY OPERATORS** > > To support such a type system, at least the null-safe de-referencing > operator(?.) would need to be introduced. > This should not break any existing application because the question mark is > not a legal character for variables' names, so no ambiguities can occur. > Other useful operators would be the Elvis operator(?:) which is a binary > operator that returns the left operand if it's not null otherwise the right > one and the double bang operator which is a unary operator that throws a > NullPointerException if its operand is null. > These aren't strictly needed, but they certainly improve the developer > experience. > > **COMPLEMENTARY KEYWORDS** > > Introducing an equivalent to the var keyword, introduced in Java 10, for > nullable types could be appropriate. > Let's say that the initializer of a variable is a literal, but the > developer later wants to assign a new value to this variable: how should > the compiler know if the type to infer is ExampleType or ExampleType?. > The var? keyword would only allow for nullable types, while the var keyword > for both nullable and non-nullable. > This, though, should probably be discussed. > > > **EXAMPLES** > > Module Flags: > > module com.example.project { // The module has unsound null safety > > } > > sound module com.example.project { // The module has sound null safety > > } > > unsound module com.example.project { // Unsound is redundant, the module > has unsound null safety > > } > > Example interaction between legacy code and modern code: > > public void someMethod(){ > String? someValue = OldAPI.getSomeValue(); // The return type of > getSomeValue is specified by the programmer to be nullable, no checks > needed > String someOtherValue = OldAPI.getSomeOtherValue(); // The return type > of getSomeValue is specified by the programmer to be non-null, the compiler > should include a NULLCHK > var inferredValue = OldAPI.getSomeValue(); // The return type of > getSomeValue is inferred to be nullable by var considering it's a platform > type > } > > **CONCLUSION** > > Discussing the technical changes needed for javac and for the JLS is > necessary, though this proposal will probably need a lot of discussion and > opinions before we get to this stage in my opinion. > The effort would probably be considerable though in the case of the > compiler. > From mariell.hoversholm at paf.com Mon May 16 07:05:38 2022 From: mariell.hoversholm at paf.com (Mariell Hoversholm) Date: Mon, 16 May 2022 09:05:38 +0200 Subject: PROPOSAL: Null safe type system In-Reply-To: References: Message-ID: How will assigning types work? Will this compile?: String? value = Optional.empty(); How would `instanceof` work with this? For example, is this legal?: String? value = "value"; assert value instanceof String; assert !(value instanceof Optional); If it is, it begs the question of how `instanceof` will truly work, given it is supposed to just check the raw type. If it is not, I wonder the implications of having the type be truly different from what the code reads. How does `null` equality tie into all this? Is this legal?: String? value = null; assert value == null; If it is, it would also imply `value != Optional.empty()`, which is not true from what I infer. If it is not, it would imply `value != null`, despite the clear initialiser setting it to `null`. If `T?` compiles down to `Optional`, how will the `T` be known? We don't have reified generics, so it's not actually part of the type. On Sun, 15 May 2022 at 20:01, Alessandro Autiero wrote: > Null safe type system > > Hello, this is my first time writing a proposal for OpenJDK, but I hope > that it will meet the quality standards. > I've looked through the discussions in the OpenJDK mailing lists, but I > couldn't find any regarding this issue, so I thought I'd propose something > on my own. > I discussed this proposal previously on the java subreddit here > < > https://www.reddit.com/r/java/comments/tiw76i/backwards_compatible_null_safe_nonnull_by_default/ > >to > collect ideas to make this proposal as comprehensive as possible. > I apologize in advance for any mistakes. > > **OVERVIEW** > > The Java Type System is made up of two different types: primitives and > objects. > While the first cannot be assigned to the special type null, the latter > can. > If an expression, for example, a method invocation, takes as an argument a > null reference a NullPointerException is thrown. > This design choice introduces the need to check for null values to make > sure that this doesn't happen. > The most common values that are checked are method parameters, especially > if the method is exposed publicly, and ones returned by method invocations. > To make the latter easier, in Java 8 the java.util.Optional wrapper class > was introduced. > Being a class, Optional is not suitable as an equivalent to, for example, > Swift's Optional which is used often as a method parameter. > Despite this obvious design choice, some very popular libraries in the Java > Ecosystem, most notably Selenium in my experience, have chosen to use > Optional in the context just described. > This indicates firmly that there is a need for better handling of null in > Java's type system. > As a matter of fact, annotations have become a sort of fix for this > issue(see the Checker Framework for example), though, in my opinion, this > issue should be resolved in the type system itself. > What I'd like to propose is to improve Java's type system by leveraging the > Optional wrapper, the module system introduced in Java 9(Jigsaw) and the > recent developments over at Project Valhalla to better handle the special > type null. > Considering that our neighbour in the JVM universe, Kotlin, has been > designed with a type system that takes into account exactly this issue, I > think that it's appropriate to discuss JetBrain's approach. > Kotlin's type system, differently from Java's, is made up only of objects. > While non-null scalar types, such as Int, use JVM primitive types as an > implementation detail, they are not part of the language. > Object types can be nullable(the name of the type is followed by a question > mark), non-null(the name of the type) or platform types(the name of the > type is followed by an exclamation mark) to provide a compatibility layer > with Java's type system. > In the latter case, the programmer should explicitly declare the type as > nullable or non-null(it's not technically needed as the compiler will issue > a warning and not an error, but it's advised), while in the others can be > inferred. > In practice, this means that, for example, the type Int and Int? are two > completely different types for the compiler. > While this approach can be appropriate when designing a new language, like > in Kotlin's case, it certainly isn't feasible in Java's case considering > its twenty years of legacy. > Now let's look at Swift's approach instead. Swift's type system allows for > nullable and non-null object values and follows the same naming conventions > as Kotlin, except platform types aren't a thing here. > Furthermore, nullable types aren't compiler magic in this case: they are an > alias for the Optional enum. > For example, the type Int? and Optional are the same type. > This also means that declaring, for example, a variable with type Int?? is > legal, as this simply translates to Optional>. > This approach is better suited for Java as we already have a very similar > class that we can leverage: java.util.Optional. > Implementing the same concept in Java wouldn't have been previously > possible because the Optional wrapper, being a class, is too slow. > Thanks to Project Valhalla, though, the Optional wrapper can be transformed > in a primitive class. > Non-null values assigned to Optional types should be autoboxed by using > Optional#of(T), nullable values should be autoboxed by using > Optional#ofNullable(T) while the null literal should be an alias of > Optional#empty(). > This can either be done with some compiler magic, like we currently do with > primitive wrappers, or by introducing a full-fledged feature to support > boxing and unboxing for classes, but this conversation is not relevant here > I think. > This means that the performance hit should no longer be a concern. > NOTE: An instance of a primitive class doesn't allow for null initializers, > though, considering the previously described boxing and unboxing mechanism > this should be syntactically legal. If this approach introduced ambiguity, > though, a better approach would be to use a value class instead. > > **CHALLENGES** > > As previously mentioned, Java's legacy makes implementing such a feature > quite hard when considering backwards compatibility. > The first step is obviously to make Optional into a value class and this is > possible without breaking anything, as far as I know. > Furthermore, it's also necessary to make sure that a developer using a Java > version with this new hypothetical type system can, for example, use a > library built using a version of Java that doesn't. > This can be technically done by treating older types as equivalent to > platform types and allowing javac to place null checks(NULLCHK operator) > when needed. > While platform types as a concept aren't strictly necessary, I think that > having older types be nullable by default is not the right approach as if > the programmer wants to declare the variable as non-null requires implicit > conversion(from ExampleType? to ExampleType), which goes against the > purpose of this type system, or a manual cast which can be perceived as > very verbose and redundant. > This challenge was already faced by Google when they transitioned to Dart > 2.12 which introduced the concept of nullable and non-null types. > Finally, the only challenge would be to make migrating a project from an > older Java version as painless as possible. > For this new type system to work effectively, types should be non-null by > default. > Though, Java's current type system has object types nullable by default. > This means that if a developer were to transition to this new hypothetical > version, everything in the codebase would break. > To face this issue, I think that introducing a flag, for example, > sound(sound null safety), in the module-info, just like open, is the best > approach. > The default flag would be, for example, unsound, so that backwards > compatibility is preserved even if a module-info is already present. > Some may argue though that migrating a whole module to sound null safety is > unlikely in major, especially enterprise, applications all at once. > This is the reason why we might consider having this flag also on a package > level by using the package info. > An idea would be to have the flag in front of the package declaration, > though there are currently no flags available for packages in package info, > having different type systems in a single module is not optimal for a > developer working on a codebase and there should also be a hierarchical > resolution of flags(for example a package is unsound but its parent module > is sound, what do we do?). > Apart from these implementation details, this approach ensures backwards > compatibility and an easy transition for developers to newer versions. > > **COMPLEMENTARY OPERATORS** > > To support such a type system, at least the null-safe de-referencing > operator(?.) would need to be introduced. > This should not break any existing application because the question mark is > not a legal character for variables' names, so no ambiguities can occur. > Other useful operators would be the Elvis operator(?:) which is a binary > operator that returns the left operand if it's not null otherwise the right > one and the double bang operator which is a unary operator that throws a > NullPointerException if its operand is null. > These aren't strictly needed, but they certainly improve the developer > experience. > > **COMPLEMENTARY KEYWORDS** > > Introducing an equivalent to the var keyword, introduced in Java 10, for > nullable types could be appropriate. > Let's say that the initializer of a variable is a literal, but the > developer later wants to assign a new value to this variable: how should > the compiler know if the type to infer is ExampleType or ExampleType?. > The var? keyword would only allow for nullable types, while the var keyword > for both nullable and non-nullable. > This, though, should probably be discussed. > > > **EXAMPLES** > > Module Flags: > > module com.example.project { // The module has unsound null safety > > } > > sound module com.example.project { // The module has sound null safety > > } > > unsound module com.example.project { // Unsound is redundant, the module > has unsound null safety > > } > > Example interaction between legacy code and modern code: > > public void someMethod(){ > String? someValue = OldAPI.getSomeValue(); // The return type of > getSomeValue is specified by the programmer to be nullable, no checks > needed > String someOtherValue = OldAPI.getSomeOtherValue(); // The return type > of getSomeValue is specified by the programmer to be non-null, the compiler > should include a NULLCHK > var inferredValue = OldAPI.getSomeValue(); // The return type of > getSomeValue is inferred to be nullable by var considering it's a platform > type > } > > **CONCLUSION** > > Discussing the technical changes needed for javac and for the JLS is > necessary, though this proposal will probably need a lot of discussion and > opinions before we get to this stage in my opinion. > The effort would probably be considerable though in the case of the > compiler. > -- *Mariell Hoversholm *(she/her) Software Developer Integrations (Slack #integration-team-public) Paf Mobile: +46 73 329 40 18 Br?ddgatan 11 SE602 22, Norrk?ping Sweden *Working remote from Uppsala* This email is confidential and may contain legally privileged information. If you are not the intended recipient, please contact the sender and delete the email from your system without producing, distributing or retaining copies thereof. Thank you. From alautiero at gmail.com Mon May 16 09:43:59 2022 From: alautiero at gmail.com (Alessandro Autiero) Date: Mon, 16 May 2022 11:43:59 +0200 Subject: PROPOSAL: Null safe type system In-Reply-To: References: Message-ID: Sorry for anyone reading this, but I didn't forward my response to the mailing list. My bad, sorry, it's my first time using one and I didn't check. Here is an extract of the last exchange: Me: The first assignment should compile, in fact it's equivalent to: Optional value = Optional.empty(); This is also the case in Swift for example. I would say that instanceof should work in this way: String? value = "value"; value instanceof String; // false value instanceof Optional; // true The reason why value is not an instance of String is that the following assignment is illegal: String? value = "value"; String implicit = value; // String? is not applicable to String String cast = (String) value; // ClassCastException Furthermore checking if a value is an instance of a nullable type should not be allowed considering type erasure: String? value = "value"; value instanceof String?; // String? is equivalent to Optional which is not something we can check against I don't think that this is an important limitation considering that this is already the case for collections for example. I think that it would be pretty obvious for developers to understand that String? is not an instance of String, so there should be no ambiguous there. The null check should check the value inside the optional type, so: String? value = null; value == null; // true String? anotherValue = "value"; anotherValue == null; // False This would imply that Optional.empty() == null, which makes sense in my opinion in this context. A problem would be that currently Optional.empty() != null which would break backwards compatibility. If otherwise we decide that Optional.empty() is not equal to null than there would be the ambiguity that you described. I think that this is something that definitely needs to be discussed. Rectification isn't an issue in my opinion in this case, there is really no reason why we would want to know the type of T in this case. Still me: I gave the null operator some more thought and I concluded that it makes no sense for Optional.empty() to be null. Let's say that a method returns potentially Optional.empty(), than if Optional.empty() == null wouldn't calling, for example, Optional#orElse on that instance throw a Null pointer exception logically? But this isn't what actually happens, so this is not something that we can do. So by logic Optional.empty() cannot be == null, but if it's not than a variable whose type is an instanceof optional and is initialized with a null value is not actually equal to null. Practically: String? value = null; value == null; // false value.isEmpty(); // true This is appropriate, but not that intuitive considering the initializer. Perhaps a better idea, keeping in mind this considerations, is to create a new construct called Option instead of building upon Optional? Perhaps someone has a better idea to solve this inconsistency Mariell Hoversholm: I don't find the concept of building around the `Optional` and hiding the type to be a good idea. Ideally, the language would differentiate between `T!` (non-null) and `T?` (nullable) explicitly in the type-system, just like Kotlin does. I'm not quite sure what the best solution to this problem would be, as I am absolutely in favour of having nullity as part of the type system (Kotlin did it well), but it is clear that hiding types is not the best way to go. Me: On second thought, maybe a distinction would be a better approach. The module system idea is still applicable effectively and we don't need to think about these complications with optional. On Mon, May 16, 2022, 09:05 Mariell Hoversholm wrote: > How will assigning types work? Will this compile?: > > String? value = Optional.empty(); > > > How would `instanceof` work with this? For example, is this legal?: > > String? value = "value"; > assert value instanceof String; > assert !(value instanceof Optional); > > If it is, it begs the question of how `instanceof` will truly work, given > it is supposed to just check the raw type. > If it is not, I wonder the implications of having the type be truly > different from what the code reads. > > > How does `null` equality tie into all this? Is this legal?: > > String? value = null; > assert value == null; > > If it is, it would also imply `value != Optional.empty()`, which is not > true from what I infer. > If it is not, it would imply `value != null`, despite the clear > initialiser setting it to `null`. > > > If `T?` compiles down to `Optional`, how will the `T` be known? We > don't have reified generics, so it's not actually part of the type. > > On Sun, 15 May 2022 at 20:01, Alessandro Autiero > wrote: > >> Null safe type system >> >> Hello, this is my first time writing a proposal for OpenJDK, but I hope >> that it will meet the quality standards. >> I've looked through the discussions in the OpenJDK mailing lists, but I >> couldn't find any regarding this issue, so I thought I'd propose something >> on my own. >> I discussed this proposal previously on the java subreddit here >> < >> https://www.reddit.com/r/java/comments/tiw76i/backwards_compatible_null_safe_nonnull_by_default/ >> >to >> collect ideas to make this proposal as comprehensive as possible. >> I apologize in advance for any mistakes. >> >> **OVERVIEW** >> >> The Java Type System is made up of two different types: primitives and >> objects. >> While the first cannot be assigned to the special type null, the latter >> can. >> If an expression, for example, a method invocation, takes as an argument a >> null reference a NullPointerException is thrown. >> This design choice introduces the need to check for null values to make >> sure that this doesn't happen. >> The most common values that are checked are method parameters, especially >> if the method is exposed publicly, and ones returned by method >> invocations. >> To make the latter easier, in Java 8 the java.util.Optional wrapper class >> was introduced. >> Being a class, Optional is not suitable as an equivalent to, for example, >> Swift's Optional which is used often as a method parameter. >> Despite this obvious design choice, some very popular libraries in the >> Java >> Ecosystem, most notably Selenium in my experience, have chosen to use >> Optional in the context just described. >> This indicates firmly that there is a need for better handling of null in >> Java's type system. >> As a matter of fact, annotations have become a sort of fix for this >> issue(see the Checker Framework for example), though, in my opinion, this >> issue should be resolved in the type system itself. >> What I'd like to propose is to improve Java's type system by leveraging >> the >> Optional wrapper, the module system introduced in Java 9(Jigsaw) and the >> recent developments over at Project Valhalla to better handle the special >> type null. >> Considering that our neighbour in the JVM universe, Kotlin, has been >> designed with a type system that takes into account exactly this issue, I >> think that it's appropriate to discuss JetBrain's approach. >> Kotlin's type system, differently from Java's, is made up only of objects. >> While non-null scalar types, such as Int, use JVM primitive types as an >> implementation detail, they are not part of the language. >> Object types can be nullable(the name of the type is followed by a >> question >> mark), non-null(the name of the type) or platform types(the name of the >> type is followed by an exclamation mark) to provide a compatibility layer >> with Java's type system. >> In the latter case, the programmer should explicitly declare the type as >> nullable or non-null(it's not technically needed as the compiler will >> issue >> a warning and not an error, but it's advised), while in the others can be >> inferred. >> In practice, this means that, for example, the type Int and Int? are two >> completely different types for the compiler. >> While this approach can be appropriate when designing a new language, like >> in Kotlin's case, it certainly isn't feasible in Java's case considering >> its twenty years of legacy. >> Now let's look at Swift's approach instead. Swift's type system allows for >> nullable and non-null object values and follows the same naming >> conventions >> as Kotlin, except platform types aren't a thing here. >> Furthermore, nullable types aren't compiler magic in this case: they are >> an >> alias for the Optional enum. >> For example, the type Int? and Optional are the same type. >> This also means that declaring, for example, a variable with type Int?? is >> legal, as this simply translates to Optional>. >> This approach is better suited for Java as we already have a very similar >> class that we can leverage: java.util.Optional. >> Implementing the same concept in Java wouldn't have been previously >> possible because the Optional wrapper, being a class, is too slow. >> Thanks to Project Valhalla, though, the Optional wrapper can be >> transformed >> in a primitive class. >> Non-null values assigned to Optional types should be autoboxed by using >> Optional#of(T), nullable values should be autoboxed by using >> Optional#ofNullable(T) while the null literal should be an alias of >> Optional#empty(). >> This can either be done with some compiler magic, like we currently do >> with >> primitive wrappers, or by introducing a full-fledged feature to support >> boxing and unboxing for classes, but this conversation is not relevant >> here >> I think. >> This means that the performance hit should no longer be a concern. >> NOTE: An instance of a primitive class doesn't allow for null >> initializers, >> though, considering the previously described boxing and unboxing mechanism >> this should be syntactically legal. If this approach introduced ambiguity, >> though, a better approach would be to use a value class instead. >> >> **CHALLENGES** >> >> As previously mentioned, Java's legacy makes implementing such a feature >> quite hard when considering backwards compatibility. >> The first step is obviously to make Optional into a value class and this >> is >> possible without breaking anything, as far as I know. >> Furthermore, it's also necessary to make sure that a developer using a >> Java >> version with this new hypothetical type system can, for example, use a >> library built using a version of Java that doesn't. >> This can be technically done by treating older types as equivalent to >> platform types and allowing javac to place null checks(NULLCHK operator) >> when needed. >> While platform types as a concept aren't strictly necessary, I think that >> having older types be nullable by default is not the right approach as if >> the programmer wants to declare the variable as non-null requires implicit >> conversion(from ExampleType? to ExampleType), which goes against the >> purpose of this type system, or a manual cast which can be perceived as >> very verbose and redundant. >> This challenge was already faced by Google when they transitioned to Dart >> 2.12 which introduced the concept of nullable and non-null types. >> Finally, the only challenge would be to make migrating a project from an >> older Java version as painless as possible. >> For this new type system to work effectively, types should be non-null by >> default. >> Though, Java's current type system has object types nullable by default. >> This means that if a developer were to transition to this new hypothetical >> version, everything in the codebase would break. >> To face this issue, I think that introducing a flag, for example, >> sound(sound null safety), in the module-info, just like open, is the best >> approach. >> The default flag would be, for example, unsound, so that backwards >> compatibility is preserved even if a module-info is already present. >> Some may argue though that migrating a whole module to sound null safety >> is >> unlikely in major, especially enterprise, applications all at once. >> This is the reason why we might consider having this flag also on a >> package >> level by using the package info. >> An idea would be to have the flag in front of the package declaration, >> though there are currently no flags available for packages in package >> info, >> having different type systems in a single module is not optimal for a >> developer working on a codebase and there should also be a hierarchical >> resolution of flags(for example a package is unsound but its parent module >> is sound, what do we do?). >> Apart from these implementation details, this approach ensures backwards >> compatibility and an easy transition for developers to newer versions. >> >> **COMPLEMENTARY OPERATORS** >> >> To support such a type system, at least the null-safe de-referencing >> operator(?.) would need to be introduced. >> This should not break any existing application because the question mark >> is >> not a legal character for variables' names, so no ambiguities can occur. >> Other useful operators would be the Elvis operator(?:) which is a binary >> operator that returns the left operand if it's not null otherwise the >> right >> one and the double bang operator which is a unary operator that throws a >> NullPointerException if its operand is null. >> These aren't strictly needed, but they certainly improve the developer >> experience. >> >> **COMPLEMENTARY KEYWORDS** >> >> Introducing an equivalent to the var keyword, introduced in Java 10, for >> nullable types could be appropriate. >> Let's say that the initializer of a variable is a literal, but the >> developer later wants to assign a new value to this variable: how should >> the compiler know if the type to infer is ExampleType or ExampleType?. >> The var? keyword would only allow for nullable types, while the var >> keyword >> for both nullable and non-nullable. >> This, though, should probably be discussed. >> >> >> **EXAMPLES** >> >> Module Flags: >> >> module com.example.project { // The module has unsound null safety >> >> } >> >> sound module com.example.project { // The module has sound null safety >> >> } >> >> unsound module com.example.project { // Unsound is redundant, the module >> has unsound null safety >> >> } >> >> Example interaction between legacy code and modern code: >> >> public void someMethod(){ >> String? someValue = OldAPI.getSomeValue(); // The return type of >> getSomeValue is specified by the programmer to be nullable, no checks >> needed >> String someOtherValue = OldAPI.getSomeOtherValue(); // The return type >> of getSomeValue is specified by the programmer to be non-null, the >> compiler >> should include a NULLCHK >> var inferredValue = OldAPI.getSomeValue(); // The return type of >> getSomeValue is inferred to be nullable by var considering it's a platform >> type >> } >> >> **CONCLUSION** >> >> Discussing the technical changes needed for javac and for the JLS is >> necessary, though this proposal will probably need a lot of discussion and >> opinions before we get to this stage in my opinion. >> The effort would probably be considerable though in the case of the >> compiler. >> > > > -- > > *Mariell Hoversholm *(she/her) > > Software Developer > > Integrations (Slack #integration-team-public) > > > Paf > > Mobile: +46 73 329 40 18 > > Br?ddgatan 11 SE602 22, Norrk?ping > > Sweden > > *Working remote from Uppsala* > > > > > This email is confidential and may contain legally privileged information. > If you are not the intended recipient, please contact the sender and delete > the email from your system without producing, distributing or retaining > copies thereof. Thank you. > From brian.goetz at oracle.com Mon May 16 13:29:49 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 16 May 2022 09:29:49 -0400 Subject: PROPOSAL: Null safe type system In-Reply-To: References: Message-ID: <33babe4b-06e4-d064-1f3c-313b37b8bac9@oracle.com> There's a few things here. First, you make an argument for "its time to bring nullability into the type system."? No arguments with your argument, but let's be aware that this is a significant project -- not only does it fundamentally affect the type system, but we have 25 years of libraries that have been written for "unknown nullity".? Just as generics was huge *and then* generifying the existing libraries was even bigger, the same relationship is in play here. While I think it is a good time to raise awareness of the issue, I think its premature to try and pick solutions here.? Because, Valhalla is going to generate some of the answers, and some new constraints, with regard to nullity. > The effort would probably be considerable though in the case of the > compiler. In general, for any serious language feature, the compiler work is never the long pole.? The long pole is designing the feature so it looks like it's always been there, both from a specification-facing and user-facing perspective. The high-order bit of your proposal is to use the Optional class as the representation for nullable values; translate `Foo?` as `Optional`.? I think this is a false economy, though; there is so much work required to make this lift, that the benefit of reusing Optional is very small, and it carries with it some constraints you might not want.? For example: ??? void m(Foo? blah) { ... }? // method descriptor (LOptional;)V ??? void m(Bar? blah) { ... }? // method descriptor (LOptional;)V error: Foo? and Bar? both have the same erasure Ouch!? You've trade one big problem for another, more subtle one: you can't overload two nullable types.? This is not only painful, it violates the principle of least astonishment.? I'd rather erase `Foo?` to Foo, and insert synthetic null checks at the boundary between `T?` and `T`, just as we insert synthetic casts at the boundary between generic code and its non-generic clients. In other words: this proposal names a problem worth solving, but picks a solution that seems (a) a false economy and (b) creates a host of new problems. > Eclipse null checking framework allows the programmer to put > @NonNullByDefault in front of the package declaration in > package-info.java. Why not create a JDK annotation instead of a flag? You should take a look at the `jspecify` effort (https://jspecify.dev/) which is a community effort trying to unify the various nullity annotations. On 5/15/2022 1:59 PM, Alessandro Autiero wrote: > Null safe type system > > Hello, this is my first time writing a proposal for OpenJDK, but I hope > that it will meet the quality standards. > I've looked through the discussions in the OpenJDK mailing lists, but I > couldn't find any regarding this issue, so I thought I'd propose something > on my own. > I discussed this proposal previously on the java subreddit here > to > collect ideas to make this proposal as comprehensive as possible. > I apologize in advance for any mistakes. > > **OVERVIEW** > > The Java Type System is made up of two different types: primitives and > objects. > While the first cannot be assigned to the special type null, the latter can. > If an expression, for example, a method invocation, takes as an argument a > null reference a NullPointerException is thrown. > This design choice introduces the need to check for null values to make > sure that this doesn't happen. > The most common values that are checked are method parameters, especially > if the method is exposed publicly, and ones returned by method invocations. > To make the latter easier, in Java 8 the java.util.Optional wrapper class > was introduced. > Being a class, Optional is not suitable as an equivalent to, for example, > Swift's Optional which is used often as a method parameter. > Despite this obvious design choice, some very popular libraries in the Java > Ecosystem, most notably Selenium in my experience, have chosen to use > Optional in the context just described. > This indicates firmly that there is a need for better handling of null in > Java's type system. > As a matter of fact, annotations have become a sort of fix for this > issue(see the Checker Framework for example), though, in my opinion, this > issue should be resolved in the type system itself. > What I'd like to propose is to improve Java's type system by leveraging the > Optional wrapper, the module system introduced in Java 9(Jigsaw) and the > recent developments over at Project Valhalla to better handle the special > type null. > Considering that our neighbour in the JVM universe, Kotlin, has been > designed with a type system that takes into account exactly this issue, I > think that it's appropriate to discuss JetBrain's approach. > Kotlin's type system, differently from Java's, is made up only of objects. > While non-null scalar types, such as Int, use JVM primitive types as an > implementation detail, they are not part of the language. > Object types can be nullable(the name of the type is followed by a question > mark), non-null(the name of the type) or platform types(the name of the > type is followed by an exclamation mark) to provide a compatibility layer > with Java's type system. > In the latter case, the programmer should explicitly declare the type as > nullable or non-null(it's not technically needed as the compiler will issue > a warning and not an error, but it's advised), while in the others can be > inferred. > In practice, this means that, for example, the type Int and Int? are two > completely different types for the compiler. > While this approach can be appropriate when designing a new language, like > in Kotlin's case, it certainly isn't feasible in Java's case considering > its twenty years of legacy. > Now let's look at Swift's approach instead. Swift's type system allows for > nullable and non-null object values and follows the same naming conventions > as Kotlin, except platform types aren't a thing here. > Furthermore, nullable types aren't compiler magic in this case: they are an > alias for the Optional enum. > For example, the type Int? and Optional are the same type. > This also means that declaring, for example, a variable with type Int?? is > legal, as this simply translates to Optional>. > This approach is better suited for Java as we already have a very similar > class that we can leverage: java.util.Optional. > Implementing the same concept in Java wouldn't have been previously > possible because the Optional wrapper, being a class, is too slow. > Thanks to Project Valhalla, though, the Optional wrapper can be transformed > in a primitive class. > Non-null values assigned to Optional types should be autoboxed by using > Optional#of(T), nullable values should be autoboxed by using > Optional#ofNullable(T) while the null literal should be an alias of > Optional#empty(). > This can either be done with some compiler magic, like we currently do with > primitive wrappers, or by introducing a full-fledged feature to support > boxing and unboxing for classes, but this conversation is not relevant here > I think. > This means that the performance hit should no longer be a concern. > NOTE: An instance of a primitive class doesn't allow for null initializers, > though, considering the previously described boxing and unboxing mechanism > this should be syntactically legal. If this approach introduced ambiguity, > though, a better approach would be to use a value class instead. > > **CHALLENGES** > > As previously mentioned, Java's legacy makes implementing such a feature > quite hard when considering backwards compatibility. > The first step is obviously to make Optional into a value class and this is > possible without breaking anything, as far as I know. > Furthermore, it's also necessary to make sure that a developer using a Java > version with this new hypothetical type system can, for example, use a > library built using a version of Java that doesn't. > This can be technically done by treating older types as equivalent to > platform types and allowing javac to place null checks(NULLCHK operator) > when needed. > While platform types as a concept aren't strictly necessary, I think that > having older types be nullable by default is not the right approach as if > the programmer wants to declare the variable as non-null requires implicit > conversion(from ExampleType? to ExampleType), which goes against the > purpose of this type system, or a manual cast which can be perceived as > very verbose and redundant. > This challenge was already faced by Google when they transitioned to Dart > 2.12 which introduced the concept of nullable and non-null types. > Finally, the only challenge would be to make migrating a project from an > older Java version as painless as possible. > For this new type system to work effectively, types should be non-null by > default. > Though, Java's current type system has object types nullable by default. > This means that if a developer were to transition to this new hypothetical > version, everything in the codebase would break. > To face this issue, I think that introducing a flag, for example, > sound(sound null safety), in the module-info, just like open, is the best > approach. > The default flag would be, for example, unsound, so that backwards > compatibility is preserved even if a module-info is already present. > Some may argue though that migrating a whole module to sound null safety is > unlikely in major, especially enterprise, applications all at once. > This is the reason why we might consider having this flag also on a package > level by using the package info. > An idea would be to have the flag in front of the package declaration, > though there are currently no flags available for packages in package info, > having different type systems in a single module is not optimal for a > developer working on a codebase and there should also be a hierarchical > resolution of flags(for example a package is unsound but its parent module > is sound, what do we do?). > Apart from these implementation details, this approach ensures backwards > compatibility and an easy transition for developers to newer versions. > > **COMPLEMENTARY OPERATORS** > > To support such a type system, at least the null-safe de-referencing > operator(?.) would need to be introduced. > This should not break any existing application because the question mark is > not a legal character for variables' names, so no ambiguities can occur. > Other useful operators would be the Elvis operator(?:) which is a binary > operator that returns the left operand if it's not null otherwise the right > one and the double bang operator which is a unary operator that throws a > NullPointerException if its operand is null. > These aren't strictly needed, but they certainly improve the developer > experience. > > **COMPLEMENTARY KEYWORDS** > > Introducing an equivalent to the var keyword, introduced in Java 10, for > nullable types could be appropriate. > Let's say that the initializer of a variable is a literal, but the > developer later wants to assign a new value to this variable: how should > the compiler know if the type to infer is ExampleType or ExampleType?. > The var? keyword would only allow for nullable types, while the var keyword > for both nullable and non-nullable. > This, though, should probably be discussed. > > > **EXAMPLES** > > Module Flags: > > module com.example.project { // The module has unsound null safety > > } > > sound module com.example.project { // The module has sound null safety > > } > > unsound module com.example.project { // Unsound is redundant, the module > has unsound null safety > > } > > Example interaction between legacy code and modern code: > > public void someMethod(){ > String? someValue = OldAPI.getSomeValue(); // The return type of > getSomeValue is specified by the programmer to be nullable, no checks needed > String someOtherValue = OldAPI.getSomeOtherValue(); // The return type > of getSomeValue is specified by the programmer to be non-null, the compiler > should include a NULLCHK > var inferredValue = OldAPI.getSomeValue(); // The return type of > getSomeValue is inferred to be nullable by var considering it's a platform > type > } > > **CONCLUSION** > > Discussing the technical changes needed for javac and for the JLS is > necessary, though this proposal will probably need a lot of discussion and > opinions before we get to this stage in my opinion. > The effort would probably be considerable though in the case of the > compiler. From alautiero at gmail.com Mon May 16 16:46:12 2022 From: alautiero at gmail.com (Alessandro Autiero) Date: Mon, 16 May 2022 18:46:12 +0200 Subject: PROPOSAL: Null safe type system In-Reply-To: <33babe4b-06e4-d064-1f3c-313b37b8bac9@oracle.com> References: <33babe4b-06e4-d064-1f3c-313b37b8bac9@oracle.com> Message-ID: My proposal to leverage Optional was in fact to make this feature feel comfortable with everyone, especially considering that this wrapper has been part of the language for quite a while now and has seen considerable use, also in JDK libraries(see the new HTTP API for example). It's clear though from the points you raised and others that were raised in a previous conversation that this approach is far from ideal. Using synthetic null checks is probably a better approach, but doing so requires having a way for the compiler to tell if a type is nullable, non-null or legacy from bytecode, for example when a library uses the old type system. The latter case is easy enough, but what about the other two? For example, Kotlin's compiler uses annotations to understand this. Here is an example: (Kotlin) fun hello(a: String){} fun anotherHello(a: String?){} (Decompiled Java code from bytecode) import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public final void hello(@NotNull String a) {} public final void anotherHello(@Nullable String a) {} I think that a very good approach would be to also leverage annotations in Java as we could use them to have a foundation for type migration. For example, if a library uses the legacy type system, but has type annotations the java compiler can infer some types more effectively instead of defaulting to platform types always. I propose though for these annotations to be reserved for the compiler in order to not create any misunderstandings about how they are supposed to be used. What do you think though about my proposal to use the module system to make this type system opt-in and the other operators that I suggested? On Mon, May 16, 2022, 15:29 Brian Goetz wrote: > There's a few things here. First, you make an argument for "its time to > bring nullability into the type system." No arguments with your argument, > but let's be aware that this is a significant project -- not only does it > fundamentally affect the type system, but we have 25 years of libraries > that have been written for "unknown nullity". Just as generics was huge > *and then* generifying the existing libraries was even bigger, the same > relationship is in play here. > > While I think it is a good time to raise awareness of the issue, I think > its premature to try and pick solutions here. Because, Valhalla is going > to generate some of the answers, and some new constraints, with regard to > nullity. > > The effort would probably be considerable though in the case of the > compiler. > > > In general, for any serious language feature, the compiler work is never > the long pole. The long pole is designing the feature so it looks like > it's always been there, both from a specification-facing and user-facing > perspective. > > The high-order bit of your proposal is to use the Optional class as the > representation for nullable values; translate `Foo?` as `Optional`. I > think this is a false economy, though; there is so much work required to > make this lift, that the benefit of reusing Optional is very small, and it > carries with it some constraints you might not want. For example: > > void m(Foo? blah) { ... } // method descriptor (LOptional;)V > void m(Bar? blah) { ... } // method descriptor (LOptional;)V > > error: Foo? and Bar? both have the same erasure > > Ouch! You've trade one big problem for another, more subtle one: you > can't overload two nullable types. This is not only painful, it violates > the principle of least astonishment. I'd rather erase `Foo?` to Foo, and > insert synthetic null checks at the boundary between `T?` and `T`, just as > we insert synthetic casts at the boundary between generic code and its > non-generic clients. > > In other words: this proposal names a problem worth solving, but picks a > solution that seems (a) a false economy and (b) creates a host of new > problems. > > Eclipse null checking framework allows the programmer to put > @NonNullByDefault in front of the package declaration in > package-info.java. Why not create a JDK annotation instead of a flag? > > > You should take a look at the `jspecify` effort (https://jspecify.dev/) > which is a community effort trying to unify the various nullity > annotations. > > > > On 5/15/2022 1:59 PM, Alessandro Autiero wrote: > > Null safe type system > > Hello, this is my first time writing a proposal for OpenJDK, but I hope > that it will meet the quality standards. > I've looked through the discussions in the OpenJDK mailing lists, but I > couldn't find any regarding this issue, so I thought I'd propose something > on my own. > I discussed this proposal previously on the java subreddit here to > collect ideas to make this proposal as comprehensive as possible. > I apologize in advance for any mistakes. > > **OVERVIEW** > > The Java Type System is made up of two different types: primitives and > objects. > While the first cannot be assigned to the special type null, the latter can. > If an expression, for example, a method invocation, takes as an argument a > null reference a NullPointerException is thrown. > This design choice introduces the need to check for null values to make > sure that this doesn't happen. > The most common values that are checked are method parameters, especially > if the method is exposed publicly, and ones returned by method invocations. > To make the latter easier, in Java 8 the java.util.Optional wrapper class > was introduced. > Being a class, Optional is not suitable as an equivalent to, for example, > Swift's Optional which is used often as a method parameter. > Despite this obvious design choice, some very popular libraries in the Java > Ecosystem, most notably Selenium in my experience, have chosen to use > Optional in the context just described. > This indicates firmly that there is a need for better handling of null in > Java's type system. > As a matter of fact, annotations have become a sort of fix for this > issue(see the Checker Framework for example), though, in my opinion, this > issue should be resolved in the type system itself. > What I'd like to propose is to improve Java's type system by leveraging the > Optional wrapper, the module system introduced in Java 9(Jigsaw) and the > recent developments over at Project Valhalla to better handle the special > type null. > Considering that our neighbour in the JVM universe, Kotlin, has been > designed with a type system that takes into account exactly this issue, I > think that it's appropriate to discuss JetBrain's approach. > Kotlin's type system, differently from Java's, is made up only of objects. > While non-null scalar types, such as Int, use JVM primitive types as an > implementation detail, they are not part of the language. > Object types can be nullable(the name of the type is followed by a question > mark), non-null(the name of the type) or platform types(the name of the > type is followed by an exclamation mark) to provide a compatibility layer > with Java's type system. > In the latter case, the programmer should explicitly declare the type as > nullable or non-null(it's not technically needed as the compiler will issue > a warning and not an error, but it's advised), while in the others can be > inferred. > In practice, this means that, for example, the type Int and Int? are two > completely different types for the compiler. > While this approach can be appropriate when designing a new language, like > in Kotlin's case, it certainly isn't feasible in Java's case considering > its twenty years of legacy. > Now let's look at Swift's approach instead. Swift's type system allows for > nullable and non-null object values and follows the same naming conventions > as Kotlin, except platform types aren't a thing here. > Furthermore, nullable types aren't compiler magic in this case: they are an > alias for the Optional enum. > For example, the type Int? and Optional are the same type. > This also means that declaring, for example, a variable with type Int?? is > legal, as this simply translates to Optional>. > This approach is better suited for Java as we already have a very similar > class that we can leverage: java.util.Optional. > Implementing the same concept in Java wouldn't have been previously > possible because the Optional wrapper, being a class, is too slow. > Thanks to Project Valhalla, though, the Optional wrapper can be transformed > in a primitive class. > Non-null values assigned to Optional types should be autoboxed by using > Optional#of(T), nullable values should be autoboxed by using > Optional#ofNullable(T) while the null literal should be an alias of > Optional#empty(). > This can either be done with some compiler magic, like we currently do with > primitive wrappers, or by introducing a full-fledged feature to support > boxing and unboxing for classes, but this conversation is not relevant here > I think. > This means that the performance hit should no longer be a concern. > NOTE: An instance of a primitive class doesn't allow for null initializers, > though, considering the previously described boxing and unboxing mechanism > this should be syntactically legal. If this approach introduced ambiguity, > though, a better approach would be to use a value class instead. > > **CHALLENGES** > > As previously mentioned, Java's legacy makes implementing such a feature > quite hard when considering backwards compatibility. > The first step is obviously to make Optional into a value class and this is > possible without breaking anything, as far as I know. > Furthermore, it's also necessary to make sure that a developer using a Java > version with this new hypothetical type system can, for example, use a > library built using a version of Java that doesn't. > This can be technically done by treating older types as equivalent to > platform types and allowing javac to place null checks(NULLCHK operator) > when needed. > While platform types as a concept aren't strictly necessary, I think that > having older types be nullable by default is not the right approach as if > the programmer wants to declare the variable as non-null requires implicit > conversion(from ExampleType? to ExampleType), which goes against the > purpose of this type system, or a manual cast which can be perceived as > very verbose and redundant. > This challenge was already faced by Google when they transitioned to Dart > 2.12 which introduced the concept of nullable and non-null types. > Finally, the only challenge would be to make migrating a project from an > older Java version as painless as possible. > For this new type system to work effectively, types should be non-null by > default. > Though, Java's current type system has object types nullable by default. > This means that if a developer were to transition to this new hypothetical > version, everything in the codebase would break. > To face this issue, I think that introducing a flag, for example, > sound(sound null safety), in the module-info, just like open, is the best > approach. > The default flag would be, for example, unsound, so that backwards > compatibility is preserved even if a module-info is already present. > Some may argue though that migrating a whole module to sound null safety is > unlikely in major, especially enterprise, applications all at once. > This is the reason why we might consider having this flag also on a package > level by using the package info. > An idea would be to have the flag in front of the package declaration, > though there are currently no flags available for packages in package info, > having different type systems in a single module is not optimal for a > developer working on a codebase and there should also be a hierarchical > resolution of flags(for example a package is unsound but its parent module > is sound, what do we do?). > Apart from these implementation details, this approach ensures backwards > compatibility and an easy transition for developers to newer versions. > > **COMPLEMENTARY OPERATORS** > > To support such a type system, at least the null-safe de-referencing > operator(?.) would need to be introduced. > This should not break any existing application because the question mark is > not a legal character for variables' names, so no ambiguities can occur. > Other useful operators would be the Elvis operator(?:) which is a binary > operator that returns the left operand if it's not null otherwise the right > one and the double bang operator which is a unary operator that throws a > NullPointerException if its operand is null. > These aren't strictly needed, but they certainly improve the developer > experience. > > **COMPLEMENTARY KEYWORDS** > > Introducing an equivalent to the var keyword, introduced in Java 10, for > nullable types could be appropriate. > Let's say that the initializer of a variable is a literal, but the > developer later wants to assign a new value to this variable: how should > the compiler know if the type to infer is ExampleType or ExampleType?. > The var? keyword would only allow for nullable types, while the var keyword > for both nullable and non-nullable. > This, though, should probably be discussed. > > > **EXAMPLES** > > Module Flags: > > module com.example.project { // The module has unsound null safety > > } > > sound module com.example.project { // The module has sound null safety > > } > > unsound module com.example.project { // Unsound is redundant, the module > has unsound null safety > > } > > Example interaction between legacy code and modern code: > > public void someMethod(){ > String? someValue = OldAPI.getSomeValue(); // The return type of > getSomeValue is specified by the programmer to be nullable, no checks needed > String someOtherValue = OldAPI.getSomeOtherValue(); // The return type > of getSomeValue is specified by the programmer to be non-null, the compiler > should include a NULLCHK > var inferredValue = OldAPI.getSomeValue(); // The return type of > getSomeValue is inferred to be nullable by var considering it's a platform > type > } > > **CONCLUSION** > > Discussing the technical changes needed for javac and for the JLS is > necessary, though this proposal will probably need a lot of discussion and > opinions before we get to this stage in my opinion. > The effort would probably be considerable though in the case of the > compiler. > > > From brian.goetz at oracle.com Mon May 16 16:52:42 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 16 May 2022 12:52:42 -0400 Subject: PROPOSAL: Null safe type system In-Reply-To: References: <33babe4b-06e4-d064-1f3c-313b37b8bac9@oracle.com> Message-ID: > My proposal to leverage Optional was in fact to make this feature feel > comfortable with everyone, especially considering that this wrapper has > been part of the language for quite a while now and has seen considerable > use, also in JDK libraries(see the new HTTP API for example). It's clear > though from the points you raised and others that were raised in a previous > conversation that this approach is far from ideal. FWIW, this is a common pattern: we observe something that can be improved, see it is a big job, and then try to make the job smaller by leveraging something we already have, even if its not a great fit.? The lesson of past attempts to do this is "if its worth doing, its worth doing right -- and that might mean you don't do it -- and that has to be OK." > What do you think > though about my proposal to use the module system to make this type system > opt-in and the other operators that I suggested? The module system may have a role to play, in identifying boundaries where we assert "this module is fully null-aware" vs "this module has legacy nullity" (which would affect type checking), and possibly even in setting defaults (our C# friends have explored this, where in some modules, the default nullity is flipped, and enforcement applied at the boundaries.) The null-aware operators are something that can be considered when all types have a nullable projection (i.e.,, `T?` is total on types.)? They've been discussed a thousand times (and the last 998 times have not added anything new to the discussion), so for now, this is something to leave for future consideration. From alautiero at gmail.com Mon May 16 20:24:07 2022 From: alautiero at gmail.com (Alessandro Autiero) Date: Mon, 16 May 2022 22:24:07 +0200 Subject: PROPOSAL: Null safe type system In-Reply-To: References: <33babe4b-06e4-d064-1f3c-313b37b8bac9@oracle.com> Message-ID: Can you think of any issues with the approach described in this conversation(synthetic null checks)? Perhaps I should write a second proposal based on this idea instead? The only issue that I could find when testing this approach against the points brought up previously by yourself and other readers is that if String! is erased to String I don't see how a runtime operator such as instanceof could tell a String! and String? apart. We could add a check using the compiler, and that's what Kotlin does for example, but that feels like "cheating" the operator if that makes sense. Il giorno lun 16 mag 2022 alle ore 18:52 Brian Goetz ha scritto: > > > My proposal to leverage Optional was in fact to make this feature feel > > comfortable with everyone, especially considering that this wrapper has > > been part of the language for quite a while now and has seen considerable > > use, also in JDK libraries(see the new HTTP API for example). It's clear > > though from the points you raised and others that were raised in a > previous > > conversation that this approach is far from ideal. > > FWIW, this is a common pattern: we observe something that can be > improved, see it is a big job, and then try to make the job smaller by > leveraging something we already have, even if its not a great fit. The > lesson of past attempts to do this is "if its worth doing, its worth > doing right -- and that might mean you don't do it -- and that has to be > OK." > > > What do you think > > though about my proposal to use the module system to make this type > system > > opt-in and the other operators that I suggested? > > The module system may have a role to play, in identifying boundaries > where we assert "this module is fully null-aware" vs "this module has > legacy nullity" (which would affect type checking), and possibly even in > setting defaults (our C# friends have explored this, where in some > modules, the default nullity is flipped, and enforcement applied at the > boundaries.) > > The null-aware operators are something that can be considered when all > types have a nullable projection (i.e.,, `T?` is total on types.) > They've been discussed a thousand times (and the last 998 times have not > added anything new to the discussion), so for now, this is something to > leave for future consideration. > > From mahdix at gmail.com Tue May 17 10:49:54 2022 From: mahdix at gmail.com (Mahdi Mohammadi) Date: Tue, 17 May 2022 11:49:54 +0100 Subject: Extended method reference - A proposal Message-ID: Hi, I have a proposal for the Java language to extend the way we use method references. Basically, instead of writing something like "*x -> obj.process(x,5)*" this should be simplified to "*obj::process(_,5)*". I believe this will simplify code, make it more concise and promote writing functional code. I'm looking for feedback from the community and also need help to make this a JEP. Here it mentions that I need help from an author, committer or reviewer to be able to do this. Thanks, Mahdi From tanksherman27 at gmail.com Tue May 17 12:07:04 2022 From: tanksherman27 at gmail.com (Julian Waters) Date: Tue, 17 May 2022 20:07:04 +0800 Subject: Extended method reference - A proposal Message-ID: Hey Mahdi, I don't quite understand what you mean, is this a more concise way to write Lambdas? (I will say I'm very interested in anything that promotes functional code in Java, but it's not very clear to me how this change might help- Maybe you could elaborate slightly more?) best regards, Julian From mariell.hoversholm at paf.com Tue May 17 12:25:25 2022 From: mariell.hoversholm at paf.com (Mariell Hoversholm) Date: Tue, 17 May 2022 14:25:25 +0200 Subject: Extended method reference - A proposal In-Reply-To: References: Message-ID: To start the process, you could outline a proper proposal? I have to warn you, though, that this seems like a bit of an arbitrary change with no particular pros besides saving a few characters. It is also much less clear from first glance: the `x -> y` syntax is clear in its intent, and is distinct from a method reference and method call. The existing syntax is also well rooted in other languages having the exact same concept, including (but not limited to) C#, ECMAScript 6, Kotlin, Groovy, and TypeScript. Having too many options for the same product is a thing, too :-). Best of luck, Mariell From brian.goetz at oracle.com Tue May 17 13:47:33 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 17 May 2022 09:47:33 -0400 Subject: Extended method reference - A proposal In-Reply-To: References: Message-ID: People requested things like this during the design of lambda.? You've got two features mushed together here: ?- Parameterization of method references with values; ?- Scala-style "wunderbar" to turn expressions with holes into lambdas Both have been proposed individually, but neither is all that attractive on their own, and don't benefit from combination. The first is a sort of "I love method references so much, I want to turn things that aren't really method references into method references."? The result is something you won't love as much. Method references are nice because they are _symbolic_; a parameterized method reference is not. The second is not intrinsically terrible, but is not very compelling.? It trades away readability for a little extra concision.? Simple uses like `_ < 10` are not all that bad, but when extended to multiple parameters, you get things like `_ < _`, which are neither that readable, *nor* are they good symbols for the `<` function!? (Also, try inverting the order; you have to fall off the "cliff" back to `(x, y) -> y < x`.)? But, "not all that bad in the simple cases" is not a raving endorsement for a language feature. On 5/17/2022 6:49 AM, Mahdi Mohammadi wrote: > Hi, > > I have a proposal for the Java language to extend the way we use method > references. > > Basically, instead of writing something like "*x -> obj.process(x,5)*" this > should be simplified to "*obj::process(_,5)*". I believe this will simplify > code, make it more concise and promote writing functional code. > > I'm looking for feedback from the community and also need help to make this > a JEP. Here it > mentions that I need help from an author, committer or reviewer to be able > to do this. > > Thanks, > Mahdi From mahdix at gmail.com Tue May 17 19:31:43 2022 From: mahdix at gmail.com (Mahdi Mohammadi) Date: Tue, 17 May 2022 20:31:43 +0100 Subject: Extended method reference - A proposal In-Reply-To: References: Message-ID: Thanks, Mariell. "Having too many options for the same product is a thing, too": I agree with this part but we already have method references which give you another option to do the same thing you could have achieved via `x->y` notation. On Tue, May 17, 2022 at 1:25 PM Mariell Hoversholm < mariell.hoversholm at paf.com> wrote: > To start the process, you could outline a proper proposal? > > I have to warn you, though, that this seems like a bit of an arbitrary > change with no particular pros besides saving a few characters. It is also > much less clear from first glance: the `x -> y` syntax is clear in its > intent, and is distinct from a method reference and method call. The > existing syntax is also well rooted in other languages having the exact > same concept, including (but not limited to) C#, ECMAScript 6, Kotlin, > Groovy, and TypeScript. Having too many options for the same product is a > thing, too :-). > > Best of luck, > Mariell > From mahdix at gmail.com Tue May 17 19:41:40 2022 From: mahdix at gmail.com (Mahdi Mohammadi) Date: Tue, 17 May 2022 20:41:40 +0100 Subject: Extended method reference - A proposal In-Reply-To: References: Message-ID: Thanks, Brian. Regarding your first point, I don't consider them a "new type of method reference" or a "parameterised method reference". This is a syntax sugar to define a lambda which only calls another function. About your second point, I agree that things like `_ < _` would be confusing and not readable at all. Maybe we can limit this to lambdas with only one argument? And, then it can be a concise way to curry functions. On Tue, May 17, 2022 at 2:47 PM Brian Goetz wrote: > People requested things like this during the design of lambda. You've got > two features mushed together here: > > - Parameterization of method references with values; > - Scala-style "wunderbar" to turn expressions with holes into lambdas > > Both have been proposed individually, but neither is all that attractive > on their own, and don't benefit from combination. > > The first is a sort of "I love method references so much, I want to turn > things that aren't really method references into method references." The > result is something you won't love as much. Method references are nice > because they are _symbolic_; a parameterized method reference is not. > > The second is not intrinsically terrible, but is not very compelling. It > trades away readability for a little extra concision. Simple uses like `_ > < 10` are not all that bad, but when extended to multiple parameters, you > get things like `_ < _`, which are neither that readable, *nor* are they > good symbols for the `<` function! (Also, try inverting the order; you > have to fall off the "cliff" back to `(x, y) -> y < x`.) But, "not all > that bad in the simple cases" is not a raving endorsement for a language > feature. > > > > On 5/17/2022 6:49 AM, Mahdi Mohammadi wrote: > > Hi, > > I have a proposal for the Java language to extend the way we use method > references. > > Basically, instead of writing something like "*x -> obj.process(x,5)*" this > should be simplified to "*obj::process(_,5)*". I believe this will simplify > code, make it more concise and promote writing functional code. > > I'm looking for feedback from the community and also need help to make this > a JEP. Here it > mentions that I need help from an author, committer or reviewer to be able > to do this. > > Thanks, > Mahdi > > > From mahdix at gmail.com Tue May 17 19:45:49 2022 From: mahdix at gmail.com (Mahdi Mohammadi) Date: Tue, 17 May 2022 20:45:49 +0100 Subject: Extended method reference - A proposal In-Reply-To: References: Message-ID: Exactly! This is a shortcut or syntax sugar to use a notation similar to a method reference but with some pre-specified arguments. So, I can write: `process(x -> obj.find(x, 10))` to call "process" function with a lambda that accepts a single argument but delegates the job to "find" function with the same argument and "10" as the second argument. Instead, I propose we should be able to write `process(obj::find(_,10))`. Basically, compiler can translate this to a normal lambda-style definition behind the scene, but for the coder, it makes writing and reading the code easier. On Tue, May 17, 2022 at 1:07 PM Julian Waters wrote: > Hey Mahdi, > > I don't quite understand what you mean, is this a more concise way to > write Lambdas? (I will say I'm very interested in anything that promotes > functional code in Java, but it's not very clear to me how this change > might help- Maybe you could elaborate slightly more?) > > best regards, > Julian > From brian.goetz at oracle.com Tue May 17 21:38:44 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 17 May 2022 17:38:44 -0400 Subject: Extended method reference - A proposal In-Reply-To: References: Message-ID: > "Having too many options for the same product is a thing, too": I agree > with this part but we already have method references which give you another > option to do the same thing you could have achieved via `x->y` notation. This sounds like more of an argument *against* method references entirely, than an argument for any particular additional ad-hoc shorthand form.? But there's a reason we have method references: they are symbolic.? A lambda encodes an arbitrary function from an ad-hoc argument list to an imperative body; a method reference is a _symbolic reference_ to an existing other thing; referring to things by their name reduces cognitive load because you don't have to analyze the lambda to see "oh, that's just delegating to method m". > Regarding your first point, I don't consider them a "new type of > method reference" or a "parameterised method reference". This is a > syntax sugar to define a lambda which only calls another function. I recommend staying away from the term "syntactic sugar"; it is almost universally misused, and is frequently invoked to downplay the significance of a request.? This *is* a new type of method reference; one that (a) invokes overload selection in a different way than other method references, and (b) infers a lambda whose arity is derived from the number of holes in the reference.? That surely is a new kind of method reference.? If you're going to make a big ask, be honest about what you're asking for. > I agree that things like `_ < _` would be confusing and not readable > at all. Maybe we can limit this to lambdas with only one argument? That may eliminate certain forms of abuse, but it also makes the feature even more limited.? And, play it out: do you believe for a minute that in the alternate universe where we had this feature, but restricted to one hole, then there is no Mahdi-prime out there who would say "I don't think this should be limited to one argument?" This feature just isn't compelling.? It's ad-hoc, has relatively limited benefit, and is motivated entirely by desire to type fewer characters, and at the same time takes a clear concept like "symbolic reference to a method" and muddies it. From tanksherman27 at gmail.com Thu May 19 04:32:39 2022 From: tanksherman27 at gmail.com (Julian Waters) Date: Thu, 19 May 2022 12:32:39 +0800 Subject: Initial discussion of solutions for issues presented in amber-dev/2022-May/007322 Message-ID: Hi all, Previously a list of issues were laid out in a previous posting to help visualize the problem and (try to help) guide future attempts at tackling it. I'd thought I'd just throw a few wild ideas out here at the moment to keep things going for now in the background as work on Amber continues. As mentioned, a significant amount of Java boilerplate stems from code that is written procedurally- Functionally if you will. These usually take the form of final classes with constructors that throw exceptions, which only contain static methods. An initial naive approach could be to allow methods to be defined in packages themselves without an enclosing class; This "top level" code is intuitively appealing to modern developers and cuts boilerplate for simple programs down to virtually zero. Of course, there is the problem that Java ultimately compiles down to classfiles, and having free floating methods in the JVM would probably require a significant amount of effort to add with diminishing returns. However, at runtime the developer has no care for whether their code compiles to classes, and classes in the metaspace are already excellent (static) method containers. It would seem a little wasteful to not reuse this and simply compile package level code into an anonymous class when javac is run. This approach does raise the interesting question of whether an entirely new class type should be introduced for this purpose so javac can utilize it when compiling "package level methods" (I'll give it the arbitrary name of ACC_PACKAGE for now); They would only allow static members, and no constructors (or anything object related) whatsoever, to be defined. Far from just being more syntactical sugar, this could possibly let us have a new minimal Klass type in the JVM with all the support for creating objects (vtable index, subclass and superclass support, and everything else object related that's defined in the base Klass that every other klass type inherits from) removed entirely. I may be entirely wrong here, but to me it would seem that the resulting tighter packing in the metaspace would result in, at the very least, improvement of memory consumption for classes that will never need it. Of course, it wouldn't hurt to allow manually defining an "ACC_PACKAGE" class (eg public package Util {}) either, such as if the developer wishes not to take up an entire package namespace for just a few utility methods. An obvious thing to do is have all methods and fields defined in these special classes be static by default without requiring the "static" keyword (In fact it would be an error for them to *not* be static at all). What would be up for discussion is whether to allow defining them differently in a way that would avoid the typical "class XXX {}" declaration (Much like what is already done with records. Maybe we could merge package and class declarations for these special classes?). There's obviously many more things to address (Should we allow nesting these classes, or nesting *in* these classes? What should the default access level be now that we don't have to be worried about subclass/protected access? Should defining package level methods be allowed in a file that also contains a regular class? And so on), but hopefully this is a good first start as a whole. (Special thanks to Mariell Hoversholm for helping out with certain areas in a private conversation) best regards, Julian From hjohn at xs4all.nl Thu May 19 07:00:32 2022 From: hjohn at xs4all.nl (John Hendrikx) Date: Thu, 19 May 2022 07:00:32 +0000 Subject: Discussion on boilerplate for code tightly bound to OOP In-Reply-To: References: Message-ID: >The problem is something that Java's had for quite some time, and is one of >the main issues that earns it quite a bad reputation[1] amongst developers >(Even if some of that disdain is outdated and has already been rectified in >newer Java releases)- The language is simply too verbose/has too much >boilerplate, and Java code itself is too tightly linked to classes[1] (at >least on the semantic level; Languages like Kotlin support other paradigms >like functional code even though they ultimately compile down to Java >classfiles). Isn't the link mentioned in [1] a parody? "Java for the Haters in 100 Seconds" seems click bait to me. The entire channel seems full of this click bait, for many languages and anything else they can get clicks for. The author even links to another video saying "just kidding". Not a very credible source for anything IMHO as the need to commercialize the content is probably more important than providing impartial and accurate information. >Of course, like I did mention above, quite a number of these issues are >already being tackled (records, accessors for arbitrary classes, talks >about deconstructors, with expressions, future reified generics, potential >operator overloading in Valhalla with values and primitives), which is a >great thing, but Java is still having trouble catching up with modern >developer expectations and other languages as a whole. According to who is it having trouble catching up? My expectations of a modern language are that it is backwards and forwards compatible, that it is predictable, stable and highly readable. Readability is something that very few languages get right. It for example means that there aren't too many different ways of accomplishing the same thing, and what ways are available should be useful to all developers in common situations in order to be recognizable. It also implies that things are not hidden from the developer. A snippet of Java code taken out of context can usually be interpreted relatively unambiguously, because the context is limited in how it can affect that snippet. A feature like a preprocessor (#define from C) would ruin this. As would import aliases as convenient as they may seem. Already static imports diminish readability somewhat which is why they're often restricted for specific uses only (like test code). There is a precarious balance between readability and conciseness; more concise code often needs more context and thus more effort from the reader to interpret. >While many of these >complaints are often fueled by hype and personal opinion and provide no >constructive value as feedback whatsoever, there are still many salient >points to address, in this case it's how the language is too forceful with >OOP. I never felt the need to write functions without a class, in fact, the class helps to add good name space for groups of related functions, avoiding repetition in the function names themselves. Java enforcing structure on your code, its novel take on one class = one file and how they're organized in packages is a large part of why it is (I refuse to say "was") so successful. In other languages, all these things we take for granted in Java are not standardized, may differ per project or even per file. Newcomers to Java may be bewildered a bit about this way of working (if they had prior programming experience), but that pays dividends when switching from code base to code base and not having to learn a new way of how things are organized every time. >It's not much of a secret that if one wants to write simple, procedural >Java, they have to fight the language substantially to do so. Classes have >to be marked as final, their constructors privated and made to throw an >exception if reflection is used on them anyway, and every method has to be >marked as static. These are self imposed restrictions, popularized by tools that will flag these as "problems". I don't see these as problems, and so these warnings get disabled. There is no need to write such a private constructor, nor is it a requirement to mark such a class as final, apart from satisfying an IMHO dubious warning that is much better addressed with the warning at the call site (calling a static method in a non-static way). Something similar happens with serialVersionUID. People satisfy this warning by adding a generated id (based on the current class structure) but have no intention of ever using the mechanism. The field is just some very ugly random noise in this case. A much more sane approach is to turn the warning off, or if you are specifically writing with serialization in mind, it is probably better to start from version 1L. The only reason to use the generated ID is if you already serialized these classes before ever giving serialization some thought and need to remain backwards compatible with serialized data from previous versions of your code. >At first glance, this might just seem like a mild >inconvenience and one would wonder why this is an issue, but this becomes >incredibly repetitive and frustrating when made to scale, which will >undoubtedly drive developers off when considering it as a language of >choice (Even with a simple "Hello World" jar, many newcomers are often put >off with how much code is required, which is the last thing we'd want to >happen). Much of this extra verbosity from having procedural code (unlike >with statically typing a variable or a method's return type for instance) >is also a tradeoff without any benefit as well, and there doesn't seem to >be much of a reason to make developers pay such heavy penalties if we can >help them out with new language features, even more so since "mutating" >value classes was already considered too painful in the amber drafts (Also >from[2] "You get more typing practice?" "The shares for keyboard >manufacturers go up?" "You can sell software to generate this omitted> automatically?"). I think not too much weight should be attached to these kinds of snark comments. >It could be argued that this could all be avoided by embracing OOP >entirely, which is certainly one of Java's biggest strengths, but there are >many instances where the procedural approach would be significantly easier >to implement the program's logic in (think the number of Java codebases >that have static only utility classes), or where forcing OOP would actually >degrade both code quality and performance (deeply nested chains of object >pointers or pointless object allocations for instance). In what way will not having to type a class wrapper around a set of (hopefully) related functions make things **significantly** easier? How will these functions be imported in another class, or are you suggesting code bases with just a single source file, inevitably leading to a huge unreadable jumble of functions? A class acts as a convenient name space to keep things organized. It is a requirement as the alternative seems to be leading to global functions. Also, what deeply nested chains of object pointers or pointless object allocations are you referring to? It seems to refer to what people new to the language might be doing, which although regrettable, is I think not so much a Java language problem but more a general lack of familiarity with OOP. IMHO it is best to stamp this out as early as possible instead of encouraging further by providing more lenient syntax. > There's also >another problem in that this forceful approach also causes newer developers >to learn rather harmful and over-the-top notions of OOP and how everything >must be a class and object- To the point that there is even an entire talk >for how to unlearn the rigid and incorrect concepts of OOP for people >coming to other languages from Java[3]. Given how popular of a language >Java already is despite the number of complaints against it, we should be >careful to not be the ones responsible for "poisoning" individuals new to >programming with the idea that everything has to be in a class- This does >no one any favours, not us, nor the learning programmers who may end up >harming the ecosystem at large once their assumptions are deeply rooted. So you are saying that other languages that use different paradigms are hurt by the paradigms in use in Java and that Java therefore should conform more to these other languages? I think Java is actually better for exactly the reason that it doesn't try to be two things, by catering both to professional and casual Java developers. Casual Java developers (by which I mean developers that are well versed in other languages, but sometimes do some Java side work) will have totally different views on what is essential to a language, heavily inspired by their language of choice. They will be writing in their primary language (C code for example), and then make minor changes to make it compile in Java. They'll be unaware of Java's paradigms, its standard library, its conventions, its ecosystem, and will be frustrated that Java is so much harder to use than their primary language. I've seen code written like this and it should not be encouraged, just like how the video mentioned in link [3] is discouraging how things are done in Java in their language of choice. Given Java's success and market share, I think those languages would benefit from being more Java like instead. >Maybe Java's current verbosity when it comes to procedural code could still >be an advantage for enterprise applications, where being as explicit as >possible with boilerplate can be helpful when reading a codebase, but that >would be a rather shortsighted view. It would be favourable for Java to >become a well liked choice for general purpose utilities such as regular >desktop applications or quick and small ones written out of convenience for >instance, and limiting it to enterprise-only would make its ideal users >only a very specific group- Not good if we want it to stay competitive. Not >to mention that a new language feature for facilitating procedural code can >indeed be explicit without much of the boilerplate of static only >classes, if designed properly. This would also help in hiding the >implementation detail of ultimately compiling down to classfiles, bypassing >the issue mentioned above entirely, since to the developer there is >technically no class involved whatsoever- Just methods. You could even get >IDE support for checking whether reflection attempted to call a constructor >at compile time too! > >In the end, it certainly doesn't help with Java's image (and potential >newcomers to the language) that a common view of it by many developers out >there is that it's reluctant to include newer language features >because "omitted> you, I'm Java"[2], which is a reputation we can do without, and I >feel that this discussion would be a good place to start in dispelling that >view of Java that many developers have today. You linked to a 9 year old Reddit post on /r/Java -- these posts are on Reddit all the time, and commonly trolled by the same group of people to express their dislike for anything Java. I'm not sure we need to cater to this group of people, as nothing will ever satisfy them. Even when their points are outdated and addressed in current versions of Java, they'll just back peddle and switch their argument to how everyone is using Java 8 still. Best regards, John Hendrikx > >Links: >[1] https://www.youtube.com/watch?v=m4-HM_sCvtQ&ab_channel=Fireship >[2] >https://www.reddit.com/r/learnprogramming/comments/1cv8rb/why_does_java_get_such_a_bad_rep/ >[3] https://www.youtube.com/watch?v=o9pEzgHorH0 > From kasperni at gmail.com Thu May 19 07:22:38 2022 From: kasperni at gmail.com (Kasper Nielsen) Date: Thu, 19 May 2022 08:22:38 +0100 Subject: Initial discussion of solutions for issues presented in amber-dev/2022-May/007322 In-Reply-To: References: Message-ID: On Thu, 19 May 2022 at 05:33, Julian Waters wrote: > Hi all, > > Previously a list of issues were laid out in a previous posting to help > visualize the problem and (try to help) guide future attempts at tackling > it. I'd thought I'd just throw a few wild ideas out here at the moment to > keep things going for now in the background as work on Amber continues. > > As mentioned, a significant amount of Java boilerplate stems from code that > is written procedurally- Functionally if you will. These usually take the > form of final classes with constructors that throw exceptions, which only > contain static methods. I don't think I've ever come across a codebase where the problem you are describing has resulted in any significant boilerplate. Yes, writing private throwing constructors is annoying. But that's about it, a minor annoyance at most. If this really is such a big issue. I think you can solve this a lot easier than by adding a new type of class. For example, by allowing interfaces to be declared final. In which case they can neither be implemented nor instantiated. And as a result, the only types of members that would be allowed on a final interface would be static fields and methods. /Kasper From lutz.horn at posteo.de Thu May 19 07:48:46 2022 From: lutz.horn at posteo.de (Lutz Horn) Date: Thu, 19 May 2022 07:48:46 +0000 Subject: Initial discussion of solutions for issues presented in amber-dev/2022-May/007322 In-Reply-To: References: Message-ID: <11B052DF-4FBE-4B8C-BC06-5A25DBC30E57@posteo.de> Hi, what I?ve learned from silently following this list is, that if you propose a solution for a problem you first have to demonstrate that 1. there is a problem 2. it is a problem worth solving 3. the proposed solution is a good solution I think your proposal fails at step 1. I don?t see much code written procedurally. And if I see such code, I argue for it to be changed. Lutz > As mentioned, a significant amount of Java boilerplate stems from code that > is written procedurally From tanksherman27 at gmail.com Fri May 20 03:12:45 2022 From: tanksherman27 at gmail.com (Julian Waters) Date: Fri, 20 May 2022 11:12:45 +0800 Subject: Initial discussion of solutions for issues presented in amber-dev/2022-May/007322 In-Reply-To: <11B052DF-4FBE-4B8C-BC06-5A25DBC30E57@posteo.de> References: <11B052DF-4FBE-4B8C-BC06-5A25DBC30E57@posteo.de> Message-ID: Hi Lutz, I did outline the issue in a previous posting to the list, though I'd appreciate feedback on how I could improve my outline of the problem. In my opinion though, just because it's how Java was initially designed shouldn't mean Java should always be written strictly OOP only. As Brian said in an earlier post, not everything has to be forced through OOP. As for (3), this isn't proposed as a catch-all solution, I'm only hoping to get the discussion started with very rough and crude suggestions so people with better ideas than me can contribute to this. best regards, Julian On Thu, May 19, 2022 at 3:48 PM Lutz Horn wrote: > Hi, > > what I?ve learned from silently following this list is, that if you > propose a solution for a problem you first have to demonstrate that > > 1. there is a problem > 2. it is a problem worth solving > 3. the proposed solution is a good solution > > I think your proposal fails at step 1. I don?t see much code written > procedurally. And if I see such code, I argue for it to be changed. > > Lutz > > > As mentioned, a significant amount of Java boilerplate stems from code > that > > is written procedurally > > From tanksherman27 at gmail.com Fri May 20 03:30:40 2022 From: tanksherman27 at gmail.com (Julian Waters) Date: Fri, 20 May 2022 11:30:40 +0800 Subject: Initial discussion of solutions for issues presented in amber-dev/2022-May/007322 In-Reply-To: References: Message-ID: Hi Kasper, Yes, it is true that this seems to just be a minor inconvenience at first, but the real issue is when you have to scale such code to bigger applications. Writing public static methods and fields over and over again is not a very fun thing to do, for instance. Besides this, I was also thinking of the possible savings that could be in the JVM itself- This was not just meant as a quality of life change for developers. You are correct that an entirely new class probably isn't the best way to tackle the problem- That's why this is labelled as a rough discussion on a mailing list, not a full blown Java Enhancement Proposal ;) (In fact, I wasn't the one that came up with the idea of package level methods) (Regarding final interfaces, I'm not sure as of yet whether you could remove object support in the runtime itself for classes acting only as namespaces with this approach unfortunately) best regards, Julian On Thu, May 19, 2022 at 3:22 PM Kasper Nielsen wrote: > > > On Thu, 19 May 2022 at 05:33, Julian Waters > wrote: > >> Hi all, >> >> Previously a list of issues were laid out in a previous posting to help >> visualize the problem and (try to help) guide future attempts at tackling >> it. I'd thought I'd just throw a few wild ideas out here at the moment to >> keep things going for now in the background as work on Amber continues. >> >> As mentioned, a significant amount of Java boilerplate stems from code >> that >> is written procedurally- Functionally if you will. These usually take the >> form of final classes with constructors that throw exceptions, which only >> contain static methods. > > > I don't think I've ever come across a codebase where the problem you are > describing has resulted in any significant boilerplate. Yes, writing > private > throwing constructors is annoying. But that's about it, a minor annoyance > at > most. > > If this really is such a big issue. I think you can solve this a lot > easier than > by adding a new type of class. For example, by allowing interfaces to be > declared final. In which case they can neither be implemented nor > instantiated. > And as a result, the only types of members that would be allowed on a final > interface would be static fields and methods. > > /Kasper > From steve at ethx.net Fri May 20 06:25:37 2022 From: steve at ethx.net (Steve Barham) Date: Fri, 20 May 2022 07:25:37 +0100 Subject: Initial discussion of solutions for issues presented in amber-dev/2022-May/007322 In-Reply-To: References: Message-ID: <15C5F8B1-3111-4A7E-8F53-C3FB94E7CE27@ethx.net> Hi Julian, > Yes, it is true that this seems to just be a minor inconvenience at first, > but the real issue is when you have to scale such code to bigger > applications. Writing public static methods and fields over and over again > is not a very fun thing to do, for instance. Proposing a change to the language, compiler and VM because you have a dislike of writing ?static? does not feel appropriate (or likely!) to me. With respect, you are approaching this problem from a very narrow angle - you stated what you would like originally, and then increased the word count in your second email. I?d encourage you to review some of the past discussions on this list and on amber-spec-experts. Brian mentioned changes to nesting rules; https://mail.openjdk.java.net/pipermail/amber-spec-experts/2020-January/001904.html might be a good start point, but I seem to recall more discussion outside of this thread, so keep reading ahead in time! As a more general topic - I?ve noticed that there?s been an uptick in social media discussion of Java, which I?d attribute to larger projects like Valhalla, Panama and Loom reaching public consciousness, as well as recognition of the QoL enjoyed in current Java. This may lead to more threads like this one arriving - or like the recent nullability thread, where bright eyed enthusiasm for ?simple? changes meets the reality of architecting a language for the next 25 years. Good problem to have, but probably somewhat time consuming to respond to. Would it be sensible to collate ?won?t do? or ?won?t do - yet!? information in some form - perhaps on https://openjdk.java.net/projects/amber/ itself? Cheers, steve (another hitherto silent lurker) From brian.goetz at oracle.com Fri May 20 13:38:09 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 20 May 2022 09:38:09 -0400 Subject: Initial discussion of solutions for issues presented in amber-dev/2022-May/007322 In-Reply-To: <15C5F8B1-3111-4A7E-8F53-C3FB94E7CE27@ethx.net> References: <15C5F8B1-3111-4A7E-8F53-C3FB94E7CE27@ethx.net> Message-ID: I think the most useful thing the community can do to help is: exactly what you just did. (Thank you!)? There are ~10M Java developers, and each of them probably have a language feature idea in their pocket.? We can't fault them for their optimism, but 99% of these ideas are unrealistic or ill-formed, and the rest are just way^3 more work and complexity than they imagine. The only way this works is if the folks who have spend more time in the community step up, like you have, and share what they've learned from their hitherto silent lurking, rather than having it fall to the ones who are also tasked with doing the actual work of stewarding, refining, and implementing platform evolution choices.? (Evolving a platform like Java requires a holistic view across not only the entire platform, history of computing, and all the other programming languages out there, but of the many possible but mutually incompatible potential futures for Java as well; this is not a very scalable activity, so taking load off of us is multiplied manyfold.) As to "would it be worth collating feature ideas" -- maybe :) Being able to reach into the list archive and pull up a relevant item is hugely valuable; there's gold in there, and its largely unindexed.? But we've also had some bad experiences along this front.? In the early days of Java, the Java bug database had an RFE issue type, and eventually it became nearly a full-time job sifting through, responding to, and cataloging them.? Eventually it became clear that the arrival rate for RFEs overwhelmed any realistic ability to evolve the language -- even back then when there were fewer constraints.? There was a voting mechanism, by which people could upvote their favorite, but there were so many items that there was a huge reinforcing effect where people only looked at the top 50 of 5000, and upvoted their favorite of those.? (For a decade, the top item was "Design by Contract".) Having a list of "maybe but not yet" features can also generate a lot of noise.? People invariably see such lists as promises. (The number of times when people ask "why didn't feature X make version Y", when there was never any plan for it to make version Y, is staggering.? Many people see a list of future features and implicitly assume they're coming in the next version, and feel lied to when it doesn't happen.)? They also see such lists as handholds for arguing, either for an alternate version of the feature design (a big distraction), or lobbying for priority (a smaller, but often more annoying distraction, because it's even more subjective.) So I encourage the community to think about how to self-organize to support us in moving the language forward.? More Steves would definitely help! On 5/20/2022 2:25 AM, Steve Barham wrote: > Hi Julian, > >> Yes, it is true that this seems to just be a minor inconvenience at first, >> but the real issue is when you have to scale such code to bigger >> applications. Writing public static methods and fields over and over again >> is not a very fun thing to do, for instance. > Proposing a change to the language, compiler and VM because you have a dislike of writing ?static? does not feel appropriate (or likely!) to me. With respect, you are approaching this problem from a very narrow angle - you stated what you would like originally, and then increased the word count in your second email. > > I?d encourage you to review some of the past discussions on this list and on amber-spec-experts. Brian mentioned changes to nesting rules;https://mail.openjdk.java.net/pipermail/amber-spec-experts/2020-January/001904.html might be a good start point, but I seem to recall more discussion outside of this thread, so keep reading ahead in time! > > As a more general topic - I?ve noticed that there?s been an uptick in social media discussion of Java, which I?d attribute to larger projects like Valhalla, Panama and Loom reaching public consciousness, as well as recognition of the QoL enjoyed in current Java. This may lead to more threads like this one arriving - or like the recent nullability thread, where bright eyed enthusiasm for ?simple? changes meets the reality of architecting a language for the next 25 years. Good problem to have, but probably somewhat time consuming to respond to. > > Would it be sensible to collate ?won?t do? or ?won?t do - yet!? information in some form - perhaps onhttps://openjdk.java.net/projects/amber/ itself? > > Cheers, > > steve (another hitherto silent lurker) From tanksherman27 at gmail.com Fri May 20 15:50:11 2022 From: tanksherman27 at gmail.com (Julian Waters) Date: Fri, 20 May 2022 23:50:11 +0800 Subject: Initial discussion of solutions for issues presented in amber-dev/2022-May/007322 Message-ID: Hi Steve, I appreciate the feedback, but I will stress that this is *not* just because I dislike writing static in classes- That was just one example I was using, I personally can adapt to language syntax rather well, but I'll put a plug in that for now. My primary concern is mostly in helping the language move forward; I really do hope Java becomes a more attractive choice among developers for... Everything, to be honest. I realize several of my worries may sound (or even be outright) trivial, but that doesn't mean that Java doesn't have issues (There is a reason why Kotlin is grossly preferred over Java for Android applications for instance). I may just not be very adept at explaining what they are, or the ideas I can come up with are impractical. But that's what the mailing list is here for, isn't it? In this particular case I did try to describe what, in my experience, is a very common complaint about the language which seemed to follow a widespread pattern that could be tackled (Utility Classes) and hoped to gather feedback/suggestions from people far more experienced than I am, so I apologize if it somehow came across as sounding like this was posted merely because I disliked having to write something a certain way- It really wasn't, and neither is this even meant to be a catch-all proposal of a solution at all! The unfortunate truth is that modern developers are very unforgiving when getting to know a new language, and one public static void main(String[] args) later they will have acquired a burning hatred for Java (If not immediately driven off by the main method, then when they have to write multiple static methods). To you and me, it's not much of a big deal, but a vast amount of people do not share the same opinion. If Java is to stay competitive, this is an ugly side of the general dev community we'll have to end up facing one way or another- There's only so much that supercharging the JVM's performance can do, if a large amount of developers still find the language a torture to work with, we're done for, and there is absolutely no shortage of hatred for the language's syntax (It's not an uncommon event for people to exclaim "Companies still use Java???" in absolute shock when asked if they have any knowledge of it!). Of course turning Java into a language overladen with sugar and having 50 ways to do the same thing is out of the question- I too enjoy how explicit it is most of the time, but we should at least try to get rid of extra verbosity that can be confirmed to not benefit anyone in any way whatsoever (Ignoring the current discussion, isn't that what records were introduced for?). I'm all for collecting features in a list like you describe, but from what I've learnt something related happened in Project Coin that apparently(?) resulted in a large fight over what feature to include, and so on, so I doubt that'd be something people would be willing to have on the Amber page. "So I encourage the community to think about how to self-organize to support us in moving the language forward. More Steves would definitely help!" That's the thing though, most of the conflict in improving the language is going to be fought between developers who are comfortable with Java, and those that already despise it with a fiery passion and often only use it out of necessity. The 2 groups (including those with experience like you mention) are going to have drastically different ideas as to what an "improvement" to the language actually is, but both sides will also typically have valid points to address, how would we decide on who to listen to? Thanks for the link to the earlier discussion though, I was not aware that it was on the list. best regards, Julian From brian.goetz at oracle.com Fri May 20 16:09:44 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 20 May 2022 12:09:44 -0400 Subject: Initial discussion of solutions for issues presented in amber-dev/2022-May/007322 In-Reply-To: References: Message-ID: > But that's what the mailing list is here for, isn't it? Actually, that's not what this mailing list is for.? This is the *development* list for the Amber project.? That means discussions on the implementation, collaboration between implementors, bug reports, experience reports, etc, on the various JEPs that are actively being developed in Project Amber. We try not to be too restrictive when discussions wander out into "wouldn't it be nice if" -- sometimes these are helpful and aligned with the work going on here -- but that's not the charter of this list. > The unfortunate truth is that modern developers are very unforgiving when > getting to know a new language, and one public static void main(String[] > args) later they will have acquired a burning hatred for Java (If not > immediately driven off by the main method, then when they have to write > multiple static methods). To you and me, it's not much of a big deal, but a > vast amount of people do not share the same opinion. If Java is to stay > competitive, this is an ugly side of the general dev community we'll have > to end up facing one way or another- There's only so much that > supercharging the JVM's performance can do, if a large amount of developers > still find the language a torture to work with, we're done for, and there > is absolutely no shortage of hatred for the language's syntax (It's not an > uncommon event for people to exclaim "Companies still use Java???" in > absolute shock when asked if they have any knowledge of it!). Of course > turning Java into a language overladen with sugar and having 50 ways to do > the same thing is out of the question- I too enjoy how explicit it is most > of the time, All of this is true.? I think the reason that you got a less-positive-than-expected response is that it is also the case that *all of this is well understood*.?? We are well aware that "public static void main" is an unfriendly onramp.? This is obvious.? Fixing it in the right way is not obvious, will take time, and in fact something that we've already laid some of the groundwork on, as referenced in the mail that Steve pointed to.? And it competes for priority and resources with all the other "obvious" ideas of how to make Java better. It is very common that people will show up on a forum like this thinking "If I just manage to convince them there's a problem, something will happen."? Believe me, we understand the problem. (Or perhaps the dozens of interlocking problems that give rise to this state of affairs.)? We also understand the other million problems facing Java, and have to balance our efforts carefully. > but we should at least try to get rid of extra verbosity that > can be confirmed to not benefit anyone in any way whatsoever (Ignoring the > current discussion, isn't that what records were introduced for?). Actually, no!? The boilerplate-reduction benefits of records is a bonus, but was not the point.? Records are about the language having a direct way to describe *data as data*.? This is a semantic upleveling, not a mere boilerplate-reduction.? The boilerplate reduction *derives from* the semantic upleveling. If we started with a goal of "reduce the boilerplate of POJO classes", the result would look more like Lombok or one of the million "bean generator" frameworks.? That would not have been a successful direction to take the language.? So no, records were not about "getting rid of verbosity", they were about *saying what you mean.*? There's a big difference. From tanksherman27 at gmail.com Sat May 21 07:25:24 2022 From: tanksherman27 at gmail.com (Julian Waters) Date: Sat, 21 May 2022 15:25:24 +0800 Subject: Initial discussion of solutions for issues presented in amber-dev/2022-May/007322 In-Reply-To: References: Message-ID: Hi Brian, "Actually, that's not what this mailing list is for. This is the *development* list for the Amber project. That means discussions on the implementation, collaboration between implementors, bug reports, experience reports, etc, on the various JEPs that are actively being developed in Project Amber." I see, I did not realize that. I'll keep that in mind from now on. "Actually, no! The boilerplate-reduction benefits of records is a bonus, but was not the point. Records are about the language having a direct way to describe *data as data*. This is a semantic upleveling, not a mere boilerplate-reduction. The boilerplate reduction *derives from* the semantic upleveling." My mistake, I guess I know something new now... "All of this is true. I think the reason that you got a less-positive-than-expected response is that it is also the case that *all of this is well understood*. We are well aware that "public static void main" is an unfriendly onramp. This is obvious. Fixing it in the right way is not obvious, will take time, and in fact something that we've already laid some of the groundwork on, as referenced in the mail that Steve pointed to. And it competes for priority and resources with all the other "obvious" ideas of how to make Java better. It is very common that people will show up on a forum like this thinking "If I just manage to convince them there's a problem, something will happen." Believe me, we understand the problem. (Or perhaps the dozens of interlocking problems that give rise to this state of affairs.) We also understand the other million problems facing Java, and have to balance our efforts carefully." At the risk of sounding selfish, I did know that the issue is already well known when I posted this, it's just that I thought having a discussion on how to fix it start in the background while the main work of Amber carries on could be helpful (Eg Should we just stop at the main method or can we scale this to many more use-cases? And so on). I once again stress that I am not demanding an obscure feature to just be added to the language with misplaced enthusiasm as Steve puts it; I'd be perfectly fine with none of the stuff I wildly threw out here not making the cut at all. Admittedly I was not aware of the work on nesting already being addressed, which I can hopefully follow while it goes on now that I know it is. Apologies for wasting the time of everyone who was listening in on this post. best regards, Julian On Sat, May 21, 2022 at 12:09 AM Brian Goetz wrote: > But that's what the mailing list is here for, isn't it? > > > Actually, that's not what this mailing list is for. This is the > *development* list for the Amber project. That means discussions on the > implementation, collaboration between implementors, bug reports, experience > reports, etc, on the various JEPs that are actively being developed in > Project Amber. > > We try not to be too restrictive when discussions wander out into > "wouldn't it be nice if" -- sometimes these are helpful and aligned with > the work going on here -- but that's not the charter of this list. > > The unfortunate truth is that modern developers are very unforgiving when > getting to know a new language, and one public static void main(String[] > args) later they will have acquired a burning hatred for Java (If not > immediately driven off by the main method, then when they have to write > multiple static methods). To you and me, it's not much of a big deal, but a > vast amount of people do not share the same opinion. If Java is to stay > competitive, this is an ugly side of the general dev community we'll have > to end up facing one way or another- There's only so much that > supercharging the JVM's performance can do, if a large amount of developers > still find the language a torture to work with, we're done for, and there > is absolutely no shortage of hatred for the language's syntax (It's not an > uncommon event for people to exclaim "Companies still use Java???" in > absolute shock when asked if they have any knowledge of it!). Of course > turning Java into a language overladen with sugar and having 50 ways to do > the same thing is out of the question- I too enjoy how explicit it is most > of the time, > > > All of this is true. I think the reason that you got a > less-positive-than-expected response is that it is also the case that *all > of this is well understood*. We are well aware that "public static void > main" is an unfriendly onramp. This is obvious. Fixing it in the right > way is not obvious, will take time, and in fact something that we've > already laid some of the groundwork on, as referenced in the mail that > Steve pointed to. And it competes for priority and resources with all the > other "obvious" ideas of how to make Java better. > > It is very common that people will show up on a forum like this thinking > "If I just manage to convince them there's a problem, something will > happen." Believe me, we understand the problem. (Or perhaps the dozens > of interlocking problems that give rise to this state of affairs.) We also > understand the other million problems facing Java, and have to balance our > efforts carefully. > > but we should at least try to get rid of extra verbosity that > can be confirmed to not benefit anyone in any way whatsoever (Ignoring the > current discussion, isn't that what records were introduced for?). > > > Actually, no! The boilerplate-reduction benefits of records is a bonus, > but was not the point. Records are about the language having a direct way > to describe *data as data*. This is a semantic upleveling, not a mere > boilerplate-reduction. The boilerplate reduction *derives from* the > semantic upleveling. > > If we started with a goal of "reduce the boilerplate of POJO classes", the > result would look more like Lombok or one of the million "bean generator" > frameworks. That would not have been a successful direction to take the > language. So no, records were not about "getting rid of verbosity", they > were about *saying what you mean.* There's a big difference. > > > From forax at univ-mlv.fr Sat May 21 11:55:52 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Sat, 21 May 2022 13:55:52 +0200 (CEST) Subject: Guard variable and being effectively final Message-ID: <928450346.11322832.1653134152729.JavaMail.zimbra@u-pem.fr> Not sure if it's an implementation bug (bad error message from the compiler) or a spec bug, hence this message to both amber-dev and amber-spec-experts. If i try to compile this code with Java 19 (which currently still uses && instead of when for a guard) interface reverse_polish_notation { static Map OPS = Map.of("+", (a, b) -> a + b, "*", (a, b) -> a * b); static int eval(List expr) { var stack = new ArrayDeque(); for(var token: expr) { final IntBinaryOperator op; stack.push(switch (token) { case String __ && (op = OPS.get(token)) != null -> { var value1 = stack.pop(); var value2 = stack.pop(); yield op.applyAsInt(value1, value2); } default -> Integer.parseInt(token); }); } return stack.pop(); } static void main(String[] args) { var expr = List.of("1", "2", "+", "3", "*", "4"); System.out.println(eval(expr)); } } I get the following error java --enable-preview --source 19 reverse_polish_notation.java reverse_polish_notation.java:17: error: local variables referenced from a guard must be final or effectively final case String __ && (op = OPS.get(token)) != null -> { ^ Note: reverse_polish_notation.java uses preview features of Java SE 19. Note: Recompile with -Xlint:preview for details. 1 error error: compilation failed Obviously the error message is funny, IntBinaryOperator is declared final so it is effectively final. In case of a lambda, final IntBinaryOperator op; Supplier supplier = () -> op = null; supplier.get() can be called several times so "op = null" does not compile. But in the example above, "op" can not be assigned more than once so maybe it should compile. regards, R?mi From dcrystalmails at gmail.com Sat May 21 19:08:50 2022 From: dcrystalmails at gmail.com (Dimitris Paltatzidis) Date: Sat, 21 May 2022 22:08:50 +0300 Subject: Guard variable and being effectively final Message-ID: Bug or feature? Let's capture the essence with a simpler, yet silly example: final boolean b; Object o = ... int i = switch (o) { case String s && (b = true) -> 1; default -> 0; }; we still get - java: local variables referenced from a guard must be final or effectively final . To better understand (hopefully) what's going on, let's take a look at static final field initialization: class C { static final int a; static {C.a = 1;} } It won't compile - java: cannot assign a value to final variable a . The problem is in C.a = 1; it has to be just a = 1; Access to field a is "overloaded" with 2 stories: 1. C.a = 1 is in defence: "I don't care, field a is final, that's illegal." 2. a = 1 is more open: "If a is not initialized, I got you, otherwise be careful" a = 1 is reduced to C.a = 1 after the first assignment. Bringing this madness to our case, the compiler just might be dealing with local variables in guards as the above case 1. (C.a = 1). We will never be able to initialize, because we have read-only access to our hands. What you are arguing for is, why the compiler plays with the rules of case 1. and not 2. which eventually renders down to, is it actually a bug? It could be a half-bug, that is, playing it too safe, by blocking write access. Sometimes, it's smart thought, the below compiles (if not IDE magic): final int a; do {a = 1;} while (false); My understanding is that, final variable initialization in guards is forbidden to defend against successful assignments, but failed conditions: final boolean b; Object o = ... int i = switch (o) { case CharSequence s && (b = Math.random() < 0.5) -> 1; case String s && (b = Math.random() < 0.5) -> 2; //No dominance problem, cause guards default -> 0; }; If o is not a String, the above should not have any problem, otherwise re-assignment is inevitable. We never know beforehand, only on runtime, but that's too late for a compile time error, isn't it. One might say, because of the second case, block compilation, which is fair, considering the below won't compile either: final int a; if (Math.random() < 0.5) {a = 1;} if (Math.random() < 0.5) {a = 2;} Again, your argument is on why single guarded-case switch expressions are restrictive as well. It might all have to do with the default: final boolean b; Object o = ... int i = switch (o) { case String s && (b = Math.random() < 0.5) -> 1; default -> {b = false; yield 0;} }; Again, we don't know if the cat is alive or dead. Of course, one could argue that in those situations, just raise a compile error at the default-case site. Adding more to the confusion, Yes; my biological compiler parses your code just fine. It just might all boil down to economic activity: "Would we get enough returns for our more out-of-jail compiler-cards investments?" static int eval(List expr) { > var stack = new ArrayDeque(); > for(var token: expr) { > final IntBinaryOperator op; > stack.push(switch (token) { > case String __ && (op = OPS.get(token)) != null -> { > var value1 = stack.pop(); > var value2 = stack.pop(); > yield op.applyAsInt(value1, value2); > } > default -> Integer.parseInt(token); > }); > } > return stack.pop(); > } > > static int eval(List expr) { var stack = new ArrayDeque(); for(var token: expr) { stack.push(Optional.ofNullable(OPS.get(token)) .map(op -> op.applyAsInt(stack.pop(), stack.pop())) .orElse(Integer.parseInt(token))); } return stack.pop(); } Of course I just defeated your purpose, didn't I, as there is an optimization twist in your code: "Don't even bother diving into the map if it ain't a String". Turns out, token can only be a String, so why even bother with pattern matching switch (of course I still get what you are trying to get across). It would be a richer story if Map::get was a pattern. From forax at univ-mlv.fr Sun May 22 11:10:04 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Sun, 22 May 2022 13:10:04 +0200 (CEST) Subject: Guard variable and being effectively final In-Reply-To: References: Message-ID: <1268017138.11482388.1653217804555.JavaMail.zimbra@u-pem.fr> To take your example, this example of code is fine final int a; if (Math.random() < 0.5) {a = 1;} while this one final int a; if (Math.random() < 0.5) {a = 1;} if (Math.random() < 0.5) {a = 1;} is not. Like in Object o = ... int i = switch (o) { case String s && (b = true) -> 1; default -> 0; }; the local variable is assigned once that why i think this code should compile. Also beware of equivalences, "final" in static final and "final" on a local variable are two different beasts. and yes, it's an example to explain why Map::get as a pattern is useful. regards, R?mi PS: in your code with an Optional, you should use orElseGet() and there is a bug in my code, it should be var value2 = stack.pop(); var value1 = stack.pop(); otherwise, it does not work if the operator is not commutative. ----- Original Message ----- > From: "Dimitris Paltatzidis" > To: "amber-dev" > Sent: Saturday, May 21, 2022 9:08:50 PM > Subject: Guard variable and being effectively final > Bug or feature? > Let's capture the essence with a simpler, yet silly example: > > final boolean b; > Object o = ... > int i = switch (o) { > case String s && (b = true) -> 1; > default -> 0; > }; > > we still get - java: local variables referenced from a guard must be final > or effectively final . > To better understand (hopefully) what's going on, let's take a look at > static final field initialization: > > class C { > static final int a; > static {C.a = 1;} > } > > It won't compile - java: cannot assign a value to final variable a . > The problem is in C.a = 1; it has to be just a = 1; Access to field a is > "overloaded" with 2 stories: > 1. C.a = 1 is in defence: "I don't care, field a is final, that's illegal." > 2. a = 1 is more open: "If a is not initialized, I got you, otherwise be > careful" > > a = 1 is reduced to C.a = 1 after the first assignment. > Bringing this madness to our case, the compiler just might be dealing with > local variables in guards as the above case 1. (C.a = 1). > We will never be able to initialize, because we have read-only access to > our hands. > > What you are arguing for is, why the compiler plays with the rules of case > 1. and not 2. which eventually renders down to, is it actually a bug? > It could be a half-bug, that is, playing it too safe, by blocking write > access. Sometimes, it's smart thought, the below compiles (if not IDE > magic): > > final int a; > do {a = 1;} while (false); > > My understanding is that, final variable initialization in guards is > forbidden to defend against successful assignments, but failed conditions: > > final boolean b; > Object o = ... > int i = switch (o) { > case CharSequence s && (b = Math.random() < 0.5) -> 1; > case String s && (b = Math.random() < 0.5) -> 2; //No dominance > problem, cause guards > default -> 0; > }; > > If o is not a String, the above should not have any problem, otherwise > re-assignment is inevitable. We never know beforehand, only on runtime, > but that's too late for a compile time error, isn't it. One might say, > because of the second case, block compilation, which is fair, considering > the > below won't compile either: > > final int a; > if (Math.random() < 0.5) {a = 1;} > if (Math.random() < 0.5) {a = 2;} > > Again, your argument is on why single guarded-case switch expressions are > restrictive as well. It might all have to do with the default: > > final boolean b; > Object o = ... > int i = switch (o) { > case String s && (b = Math.random() < 0.5) -> 1; > default -> {b = false; yield 0;} > }; > > Again, we don't know if the cat is alive or dead. > Of course, one could argue that in those situations, just raise a compile > error at the default-case site. > > Adding more to the confusion, Yes; my biological compiler parses your code > just fine. It just might all boil down to economic activity: > "Would we get enough returns for our more out-of-jail compiler-cards > investments?" > > static int eval(List expr) { >> var stack = new ArrayDeque(); >> for(var token: expr) { >> final IntBinaryOperator op; >> stack.push(switch (token) { >> case String __ && (op = OPS.get(token)) != null -> { >> var value1 = stack.pop(); >> var value2 = stack.pop(); >> yield op.applyAsInt(value1, value2); >> } >> default -> Integer.parseInt(token); >> }); >> } >> return stack.pop(); >> } >> >> static int eval(List expr) { > var stack = new ArrayDeque(); > for(var token: expr) { > stack.push(Optional.ofNullable(OPS.get(token)) > .map(op -> > op.applyAsInt(stack.pop(), stack.pop())) > .orElse(Integer.parseInt(token))); > } > return stack.pop(); > } > > Of course I just defeated your purpose, didn't I, as there is an > optimization twist in your code: "Don't even bother diving into the map if > it ain't a String". > Turns out, token can only be a String, so why even bother with pattern > matching switch (of course I still get what you are trying to get across). > It would be a richer story if Map::get was a pattern. From dcrystalmails at gmail.com Sun May 22 13:47:56 2022 From: dcrystalmails at gmail.com (Dimitris Paltatzidis) Date: Sun, 22 May 2022 16:47:56 +0300 Subject: Guard variable and being effectively final Message-ID: > > the local variable is assigned once that why i think this code should compile. > > Agree. PS: in your code with an Optional, you should use orElseGet() Nice catch! From maurizio.cimadamore at oracle.com Mon May 23 17:45:03 2022 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 23 May 2022 18:45:03 +0100 Subject: Guard variable and being effectively final In-Reply-To: <928450346.11322832.1653134152729.JavaMail.zimbra@u-pem.fr> References: <928450346.11322832.1653134152729.JavaMail.zimbra@u-pem.fr> Message-ID: The compiler behavior seems to be in sync with the spec: From [1]: "Any variable that is used but not declared in the guarding expression of a guarded pattern must either be final or effectively final (4.12.4)." And, from [2]: "Any variable that is used but not declared in a |when| expression must be either final or effectively final (4.12.4 )" As for your question of "why doesn't it work", I think it can be decomposed into two questions: * why is a "when" expression restricted to only mention final/effectively-final variables? * even under the constraint of final/effectively final, why isn't "when" allowed to "initialize" a final variable? Both of these add some asymmetries when it comes to refactoring the switch into a chain of if/else. Maurizio [1]: https://docs.oracle.com/javase/specs/jls/se18/preview/specs/patterns-switch-jls.html#jls-6.3.3.1 [2]: http://cr.openjdk.java.net/~gbierman/PatternSwitchPlusRecordPatterns/PatternSwitchPlusRecordPatterns-20220407/specs/patterns-switch-jls.html On 21/05/2022 12:55, Remi Forax wrote: > Not sure if it's an implementation bug (bad error message from the compiler) or a spec bug, > hence this message to both amber-dev and amber-spec-experts. > > If i try to compile this code with Java 19 (which currently still uses && instead of when for a guard) > > interface reverse_polish_notation { > static Map OPS = > Map.of("+", (a, b) -> a + b, "*", (a, b) -> a * b); > > static int eval(List expr) { > var stack = new ArrayDeque(); > for(var token: expr) { > final IntBinaryOperator op; > stack.push(switch (token) { > case String __ && (op = OPS.get(token)) != null -> { > var value1 = stack.pop(); > var value2 = stack.pop(); > yield op.applyAsInt(value1, value2); > } > default -> Integer.parseInt(token); > }); > } > return stack.pop(); > } > > static void main(String[] args) { > var expr = List.of("1", "2", "+", "3", "*", "4"); > System.out.println(eval(expr)); > } > } > > I get the following error > > java --enable-preview --source 19 reverse_polish_notation.java > reverse_polish_notation.java:17: error: local variables referenced from a guard must be final or effectively final > case String __ && (op = OPS.get(token)) != null -> { > ^ > Note: reverse_polish_notation.java uses preview features of Java SE 19. > Note: Recompile with -Xlint:preview for details. > 1 error > error: compilation failed > > Obviously the error message is funny, IntBinaryOperator is declared final so it is effectively final. > > In case of a lambda, > final IntBinaryOperator op; > Supplier supplier = () -> op = null; > > supplier.get() can be called several times so "op = null" does not compile. > > But in the example above, "op" can not be assigned more than once so maybe it should compile. > > regards, > R?mi From izzeldeen03 at gmail.com Tue May 31 17:35:38 2022 From: izzeldeen03 at gmail.com (Izz Rainy) Date: Tue, 31 May 2022 18:35:38 +0100 Subject: Purpose of parenthesised patterns Message-ID: With guard patterns removed and no composite "and"/"or" patterns, what's the purpose of parenthesised patterns? Is it simply an oversight, or am I missing something? From attila.kelemen85 at gmail.com Thu May 12 18:31:21 2022 From: attila.kelemen85 at gmail.com (Attila Kelemen) Date: Thu, 12 May 2022 18:31:21 -0000 Subject: TemplatedString feedback and extension - logging use-case, lazy values, blocks In-Reply-To: References: <74E94FCF-F3A4-4101-85B0-1A6CB0B6C4DF@oracle.com> Message-ID: Also, btw, is there a reference implementation I can play with? Attila Kelemen ezt ?rta (id?pont: 2022. m?j. 12., Cs, 20:07): > > It seems I did too much extrapolation after sloppy reading. > Sorry for that. > > Anyway, this seems nice. Just one thing (which is partially mentioned > in the alternatives section of the JEP). The syntax looks a bit > bizarre to me (even though it isn't ambiguous), because this adds an > additional conceptual weight to the language given that it looks like > as if the templated string instance is the method name. > > In fact, the syntax `MY_POLICY."\{x};\{y}"` could be moved out from > this JEP, and simply provide a method both ways: > > - `TemplatedString.format(TemplatePolicy)` > - `TemplatePolicy.apply(TemplatedString)` (already in JEP). > > If we ever needed the JEP provided sugar, then I would try with > something more generic that would benefit other things as well > consistently, not just special case string templates. Since as I can > see the main benefit of the syntax is that we don't need to bother > with parenthesis. > > But otherwise, this feature would be indeed very useful. Though, > I can already see people abusing a TO_STRING policy (which will > surely be provided in commons-lang or similar, if not the JDK) :) > > Attila > > Jim Laskey ezt ?rta (id?pont: 2022. m?j. > 12., Cs, 19:25): > > > > [Note that all code samples below are speculative and subject to change. There is also an assumption reader has read and has an understanding of the JEP draft. https://openjdk.java.net/jeps/8273943] > > > > I've had a chance to isolate some examples to demonstrate an approach for deferred evaluation using suppliers. > > > > Starting with the general pattern for a (string result) policy as follows; > > > > static final StringPolicy MY_SP = ts -> { > > StringBuilder sb = new StringBuilder(); > > Iterator fragmentsIter = ts.fragments().iterator(); > > for (Object value : ts.values()) { > > sb.append(fragmentsIter.next()); > > sb.append(value); > > } > > sb.append(fragmentsIter.next()); > > return sb.toString(); > > }; > > > > ... > > > > int x = 10, y = 20; > > String result = MY_SP."\{x} + \{y} = \{x + y}"; // The result will be "10 + 20 = 30". > > > > To introduce deferred evaluation we can add a test for java.util.function.Supplier and > > use the supplier's value instead; > > > > static final StringPolicy MY_SP = ts -> { > > StringBuilder sb = new StringBuilder(); > > Iterator fragmentsIter = ts.fragments().iterator(); > > for (Object value : ts.values()) { > > sb.append(fragmentsIter.next()); > > //------------------------------------------ > > if (value instanceof Supplier supplier) { > > sb.append(supplier.get()); > > } else { > > sb.append(value); > > } > > //------------------------------------------ > > } > > sb.append(fragmentsIter.next()); > > return sb.toString(); > > }; > > > > ... > > > > static final DateTimeFormatter HH_MM_SS = DateTimeFormatter.ofPattern("HH:mm:ss"); > > static final Supplier TIME_STAMP = () -> LocalDateTime.now().toLocalTime().format(HH_MM_SS); > > > > ... > > > > String message = MY_SP."\{TIME_STAMP}: Received response"; // The result will be "13:56:48: Received response". > > > > In this case, the formatting of the time stamp is deferred until the policy is applied. This example can be expanded to test for > > java.util.concurrent.Future and java.util.concurrent.FutureTask. The advantage of futures > > over Supplier is that they are only evaluated once; > > > > static final StringPolicy MY_SP = ts -> { > > StringBuilder sb = new StringBuilder(); > > Iterator fragmentsIter = ts.fragments().iterator(); > > for (Object value : ts.values()) { > > sb.append(fragmentsIter.next()); > > //------------------------------------------ > > if (value instanceof Future future) { > > if (future instanceof FutureTask task) { > > task.run(); > > } > > > > try { > > sb.append(future.get()); > > } catch (InterruptedException | ExecutionException e) { > > throw new RuntimeException(e); > > } > > } else if (value instanceof Supplier supplier) { > > sb.append(supplier.get()); > > } else { > > sb.append(value); > > } > > //------------------------------------------ > > } > > sb.append(fragmentsIter.next()); > > return sb.toString(); > > }; > > > > > > To answer Attila's comments. 1) Nothing is computed unless the policy performs an action, so a logging policy > > that has an if(ENABLED) around it's main code and static final ENABLED = false; will have little or no overhead. > > 2) One of the primary goals of template policies is to screen inputs to prevent injection attacks. So a well written policy should not be > > any more susceptible that a println statement. There is no interpretation or a need for interpretation in string templates. Policies may > > add interpretation, but then the onus is on the policy > > > > Cheers, > > > > -- Jim > > > > > > > > On May 7, 2022, at 11:52 AM, Jim Laskey wrote: > > > > [OOTO] > > > > I?ve already done examples of all of the items in your list (all good), but i?m glad you brought these topics up for open discussion. I?ll sit back a see what others have to say. > > > > However, I would like to point out that deferred evaluation requires an agreement from the policy to special case suppliers (or futures). This is something we don?t really want to bake in. Every policy should be free to pick and choose what it does. > > > > Another approach is to use policy chaining where one policy in the chain evaluates suppliers and futures to produce a new TemplatedString to be processed by the next policy in the chain. > > > > The ugliness of lambdas in an embedded expression is made worse by the fact it has to be cast to Supplier or Future since the default type for embedded expressions is Object. In all my examples, I?ve out of lined these types of expressions and used a local or better yet a global constant variable (ex., TIME_STAMP) to use in the embedded expression. I think this technique makes the string template read better. > > > > When I?m back the end of next week I can post some examples. > > > > Cheers, > > > > ?Jim > > > > After this response we should move the discussion to the amber-dev mailing list. > > > > ?? > > > > On May 7, 2022, at 10:58 AM, Adam Juraszek wrote: > > > > ?The TemplatedString proposal is amazing; I can see using it in many places. > > I have a few comments that I have not seen addressed. > > > > Logging > > ======= > > > > One place where some kind of string templating is often used is logging. > > Most logging frameworks use a pair of braces {}. TemplatedStrings have the > > potential to revolutionize it. > > > > Log record usually consists of the following parts that the programmer > > needs to provide: > > * log level (such as error, info) > > * message (such as "Oh no, the value {} is negative") > > * parameters (such as -42) > > * exception/throwable (only sometimes) > > > > Let's assume that TemplatedString is performant and virtually free, then > > these options would be possible. I cannot say which syntax the logging > > frameworks would choose: > > > > logger.error."Oh no, the value \{value} is negative\{ex}"; > > logger.error("Oh no, the value \{value} is negative", ex); > > logger."Oh no, the value \{value} is negative".error(ex); > > > > Versus the existing: > > > > logger.error("Oh no, the value {} is negative", value, ex); > > > > Lazy Logging > > ============ > > > > Some logging frameworks such as Log4j2 allow lazy evaluation for example: > > > > logger.trace("Number is {}", () -> getRandomNumber()); > > > > I must admit that doing the same using TemplatedStrings is ugly and the 6 > > distinct characters introducing a lazily evaluated value are impractical: > > > > logger.trace("Number is \{() -> getRandomNumber()}"); > > > > Can we offer something better using TemplatedStrings? The JEP already talks > > about evaluation. For example, this is valid: > > > > int index; > > String data = STR."\{index=0}, \{index++}"; > > > > and mentions that the TemplatedString provides a list of values: > > > > TemplatedString ts = "\{x} plus \{y} equals \{x + y}"; > > List values = ts.values(); > > > > I want to propose an additional syntax. The escape sequence \( would denote > > a lazily computed value - probably stored as a Supplier. Because the value > > may not be evaluated, it must only refer to effectively final variables > > (the same rules as for lambda expressions apply). > > > > The following should be illegal: > > > > int index; > > String data = STR."\(index=0), \(index++)"; > > > > The logging use-case could be expressed more naturally: > > > > logger.trace("Number is \(getRandomNumber())"); > > > > I personally can see a parallel between \(...) and ()-> as both use > > parentheses. This unfortunately is the exact opposite of the syntax of > > closures in Groovy, where {123} is "lazy" and (123) produces the value > > immediately. Different languages, different choices. > > > > When should this lazy expression be evaluated, and who is responsible for > > it? It can either be: > > * when ts.values() is called; > > * when the value is accessed (via ts.get(N) or .next() on the List's > > Iterator); > > * manually when it is consumed by the author of the policy using an > > instanceof check for Supplier. > > > > I keep this as an open question (first we need to find out if this is even > > a useful feature). It also remains open what to do when a user provides a > > Supplier manually: > > > > Supplier s = () -> 123 > > logger.trace("Number is \{s} \(s)"); > > > > Another way to express a Supplier is via a method reference, which should > > also be supported and would suggest the equivalence of the following: > > > > logger.trace("Number is \{this::getRandomNumber}"); > > logger.trace("Number is \(this.getRandomNumber())"); > > > > Blocks > > ====== > > > > What if the computation of the value requires multiple statements? > > > > STR."The intersection of \{a} and \{b} is \{((Supplier) ()-> {var c = new > > HashSet<>(a); c.retainAll(b); return c;}).get()}" > > STR."The intersection of \{a} and \{b} is \{switch(1){default:var c = new > > HashSet<>(a); c.retainAll(b); yield c;}}" > > > > These are (as far as my imagination goes) the easiest ways to turn a block > > into an expression. > > > > What if we introduced a special syntax for blocks? One option would be > > simply doubling the braces: > > > > STR."The intersection of \{a} and \{b} is \{{var c = new HashSet<>(a); > > c.retainAll(b); yield c;}}" > > > > The last closing brace is probably not needed but keeping them balanced > > will help dumb editors with highlighting. > > > > This could be a more general syntax useful even outside TemplatedStrings > > (especially in final field initialization): > > > > var intersection = {var c = new HashSet<>(a); c.retainAll(b); yield c;}; > > > > Lazy Blocks > > =========== > > > > We may want to combine the two proposed features and compute the > > intersection lazily when logging: > > > > logger.trace("The intersection of \{a} and \{b} is \({var c = new > > HashSet<>(a); c.retainAll(b); return c;})") > > > > which would be equivalent to this already supported syntax provided that > > the policy understands Supplier values: > > > > logger.trace("The intersection of \{a} and \{b} is \{() -> {var c = new > > HashSet<>(a); c.retainAll(b); return c;}}") > > > > If we introduce \( as described in Lazy Logging above and say that > > \(expression) is equivalent to \{() -> expression}, then \({statements}) > > would naturally be equivalent to \{() -> {statements}}. > > > > Literals > > ======== > > > > It was already mentioned in the JEP that it is possible to create a policy > > returning a JSON object. We can also think of them as a way to express > > literals. Some Java types already have a way to parse a String. Some > > examples are: > > > > DURATION."PT20.345S" > > Duration.from("PT20.345S") > > > > COMPLEX."1+3i" > > Complex.from(1, 3) > > > > US_DATE."7/4/2022" > > LocalDate.of(2022, 7, 4) > > > > This is almost as powerful as what C++ offers ( > > https://en.cppreference.com/w/cpp/language/user_literal). > > > > Conclusion > > ========== > > > > I am very happy with the current proposal and tried to explore some > > possible extensions. I proposed a syntax for lazy evaluation of values, > > blocks as values, and a combination thereof. I would like to hear from you, > > whether this is something worth considering, and whether there is demand > > for it. > > > > Regards > > Adam > > > > From attila.kelemen85 at gmail.com Thu May 12 19:50:48 2022 From: attila.kelemen85 at gmail.com (Attila Kelemen) Date: Thu, 12 May 2022 19:50:48 -0000 Subject: [External] : Re: TemplatedString feedback and extension - logging use-case, lazy values, blocks In-Reply-To: References: <74E94FCF-F3A4-4101-85B0-1A6CB0B6C4DF@oracle.com> Message-ID: Thanks, that is what I expected, works for me. Jim Laskey ezt ?rta (id?pont: 2022. m?j. 12., Cs, 21:19): > > I'm afraid you have to build your own > > git clone https://github.com/openjdk/amber.git > cd amber > git branch templated-strings > > then build using regular instructions > >