From davidalayachew at gmail.com Fri Sep 9 13:41:11 2022 From: davidalayachew at gmail.com (David Alayachew) Date: Fri, 9 Sep 2022 09:41:11 -0400 Subject: My experience with Sealed Types and Data-Oriented Programming Message-ID: Hello Amber Team, I just wanted to share my experiences with Sealed Types and Data-Oriented Programming. Specifically, I wanted to show how things turned out when I used them in a project. This project was built from the ground up to use Data-Oriented Programming to its fullest extent. If you want an in-depth look at the project itself, here is a link to the GitHub. If you clone it, you can run it right now using Java 18 + preview features. The GitHub repo = https://github.com/davidalayachew/RulesEngine Current version = https://github.com/davidalayachew/RulesEngine/commit/0e7fa42db4bbebaa3aa30f882645226d28e63ff4 The project I am building is essentially an Inference Engine. This is very similar to Prolog, where you can give the language a set of rules, and then the language can use those rules to make logical deductions if you ask it a question. The only difference is that my version accepts plain English sentences, as opposed to requiring you to know syntax beforehand. Here is a snippet from the logs to show how things work. David is a programmer -------- OK Every programmer is an engineer -------- OK Every engineer is an artist -------- OK Is David an artist? -------- CORRECT As you can see, it takes in natural English and gleans rules from it, then uses those rules to perform logical deductions when a query is later made. Sealed types made this really powerful to work with because it helped me ensure that I was covering every edge case. I used a sealed interface to hold all of the possible rule types, then would use switch expressions to ensure that all possible branches of my code were handled if the parameter is of that sealed type. For the most part, it was a pleasant experience where the code more or less wrote itself. The part that I enjoyed the most about this was the ease of refactoring that sealed types, records, and switch expressions allowed. This project grew in difficulty very quickly, so I found myself refactoring my solution many times. Records automatically update all of their methods when you realize that that record needs to/shouldn't have a field. And switch expressions combined with sealed types ensured that if I added a new permitted subclass, I would have to update all of my methods that used switch expressions. That fact especially made me gravitate to using switch expressions to get as much totality as possible. When refactoring your code, totality is a massive time-saver and bug-preventer. Combine that with the pre-existing fact that interfaces force all subclasses to have the instance methods defined, and I had some powerful tools for refactoring that allowed me to iterate very quickly. I found that to be especially powerful because, when dealing with software that is exposed to the outside world, making that code easy to refactor is a must. The outside world is constantly changing, so it is important that we can change quickly too. Therefore, I really want to congratulate you all on creating such a powerful and expressive feature. I really enjoyed building this project, and I'm excited to add a lot more functionality to it. However, while I found working with sealed types and their permitted subclasses to be a smooth experience, I found the process of turning data from untyped Strings into one of the permitted subclasses to be a rather frustrating and difficult experience. At first glance, the solution looks really simple - just make a simple parse method like this. public static MySealedType parse(String sanitizedUserInput) { //if string can safely be turned into Subclass1, then store into Subclass1 and return //else, attempt for all other subclasses //else, fail because string must be invalid to get here } Just like that, I should have my gateway into the world of strongly typed, expressive data-oriented programming, right? Unfortunately, maintaining that method got ugly fast. For starters, I don't have a small handful of permitted subclasses, I have many of them. Currently, there are 11, but I'm expecting my final design to have a little over 30 subclasses total. On top of that, since my incoming strings are natural English, each of my if branches carries non-trivial amounts of logic so that I can perform the necessary validation against all edge cases. To better explain the complexity, I had created a complex regex with capture groups for each permitted subclass, and then used that to validate the incoming String. If the regex matches, pass the values contained in the capture groups onto the constructor as a List, then return the subclass containing the data of the string. At first, this worked well, but as the number of subclasses grew, this got very difficult to maintain as well. This difficulty was twofold. Problem 1 - I found that my regex would frequently be misaligned with my constructors during refactoring. If I decided that a record needed a new field, or that a field should be removed, I would update the record but not the regex, and then find errors during runtime. In fact, I sometimes didn't find errors during runtime because List had the same number of elements as the constructor was expecting, but the fields were not aligned to the right index. This cost me a lot of development time. Problem 2 - I found that there wasn't an easy way to make sure that all of my subclasses followed all the rules that they were supposed to, and thus, I kept forgetting to implement those rules in one way or another every time I refactored. For example, for problem 1, I said that every subclass must have a regex. However, I couldn't find some compiler enforced way to enforce this. * Interfaces are only able to enforce instance methods. However, I can't have my regex be an instance method. That would be putting the cart before the horse - I am using the regex to create an instance, so the instance method is not helpful here * If I used a sealed abstract class instead and had permitted subclasses instead of permitted records, I still couldn't store my regex as a final instance field for the above reason. * In Java, static methods cannot be overrided, so I can't use a static method on my sealed interface. The static method would belong to the interface, not to the child subclasses. * And a static final field would not work for the same reason above. I ran into similar troubles when creating the alternative constructors for each permitted subrecord. Almost all of the above bulleted points apply, with the only exception being that for an abstract class, you can *technically* force your subclasses to call the super constructor. However, that did very little to help me solve my problem. Maybe I'm wrong and this is the silver bullet I am looking for, but it certainly doesn't seem like it. Therefore, I stuck to my original solution of a sealed interface with permitted subrecords. But back to my original point. I had 2 problems - misalignment and no enforcement of my abstract rules. Since I kept changing and creating and recreating more and more subclasses, these 2 pain points became bigger and bigger thorns in my side. Worse yet, I actually wanted to add more rules to make these classes even easier to work with, but decided not to after seeing the above difficulty. To alleviate problem 1, I stored my regexes in the records themselves, so that I would be forced to see the regex each time I looked at the record. For the most part, that solution seems to be good enough to deal with regex misalignment. To alleviate problem 2, I decided to brute force some totality and enforcement of my own. I fully admit, the solution I came up with here is a bad practice and something no one should imitate, but I found this to be the most effective way for me to enforce the rules I needed. I used reflection on my sealed interface. I got the sealed type class, called Class::getPermittedSubclasses, looped through the subclasses, did an unsafe cast from Class to Class (because ::getPermittedSubclasses doesn't do that on its own for some reason???), called Class::getConstructor with the parameter being List.class (to represent the list of strings), and then used that to construct a Map, MySealedInterface>>. I didn't do the same for the regex because that monstrosity of code included a Map::put which would take in the regex and the constructor. Therefore, it was pretty easy to remember both since they were right next to each other, and JVM will error out on startup if I forget to include my constructor. So, I have effectively solved both of my problems, but in less than desirable ways. For problem 2, one analogy that kept popping into my head was the idea of there being 2 islands. The island on the right has strong types, totality, pattern matching, and more. Meanwhile the island on the left is where everything is untyped and just strings. There does exist a bridge between the 2, but it's either difficult to make, doesn't scale very well, or not very flexible. This analogy really helped realize my frustration with it because it actually showed why I like Java enums so much. You can use the same analogy as above. The island on the right has ::ordinal, ::name, ::values, enums having their own instance fields and methods, and even some powerful tools like EnumSet and EnumMap. But what really ties it all together is that, there is a very clear and defined bridge between the left and the right - the ::valueOf method. Having this centralized pathway between the 2 made working with enums a pleasure and something I always liked to use when dealing with my code's interactions with the outside world. That ::valueOf enforced a constraint against all incoming Strings. And therefore, it allowed me to just perform some sanitizations along the way to make sure that that method could do it's job (uppercase all strings, remove non-identifier characters, etc). If it wasn't for JEP 301, I would call enums perfect. I just wish that there was some similar centralized pathway between data-oriented programming and the outside world. Some way for me to define on my sealed type, a method to give me a pathway to all of the permitted subclasses. Obviously, I can build it on my own, but that is where most of my pain points came from. Really, having some way to enforce that all of my subclasses have a similar class level validation logic and a similar class level factory/constructor method is what I am missing. That is the summary of my thoughts. Please do not misinterpret the extended discussion on the negatives to mean that I found the negative to be even equal to, let alone more than, the positives. I found this to be an overwhelmingly pleasant experience. Once I got my data turned into a type, everything flowed perfectly. It was just difficult to get it into a type in the first place, and it took a lot of words for me to explain why. Thank you all for your time and your help! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From numeralnathan at gmail.com Fri Sep 9 15:31:21 2022 From: numeralnathan at gmail.com (Nathan Reynolds) Date: Fri, 9 Sep 2022 09:31:21 -0600 Subject: My experience with Sealed Types and Data-Oriented Programming In-Reply-To: References: Message-ID: I am not even an amateur with parsers. It seems like you need a backtracking parser that converts strings into records. When the parser finds a construct that matches a record, it creates the record. The code that links the grammar to the records needs to be linked at the Java code level so that a change in a record breaks the grammar and vis-a-versa. I don't of anything like this, but that doesn't say much since writing this was pushing the boundaries of my knowledge. On Fri, Sep 9, 2022 at 7:41 AM David Alayachew wrote: > Hello Amber Team, > > I just wanted to share my experiences with Sealed Types and Data-Oriented > Programming. Specifically, I wanted to show how things turned out when I > used them in a project. This project was built from the ground up to use > Data-Oriented Programming to its fullest extent. If you want an in-depth > look at the project itself, here is a link to the GitHub. If you clone it, > you can run it right now using Java 18 + preview features. > > The GitHub repo = https://github.com/davidalayachew/RulesEngine > > Current version = > https://github.com/davidalayachew/RulesEngine/commit/0e7fa42db4bbebaa3aa30f882645226d28e63ff4 > > The project I am building is essentially an Inference Engine. This is very > similar to Prolog, where you can give the language a set of rules, and then > the language can use those rules to make logical deductions if you ask it a > question. The only difference is that my version accepts plain English > sentences, as opposed to requiring you to know syntax beforehand. > > Here is a snippet from the logs to show how things work. > > David is a programmer > -------- OK > Every programmer is an engineer > -------- OK > Every engineer is an artist > -------- OK > Is David an artist? > -------- CORRECT > > As you can see, it takes in natural English and gleans rules from it, then > uses those rules to perform logical deductions when a query is later made. > > Sealed types made this really powerful to work with because it helped me > ensure that I was covering every edge case. I used a sealed interface to > hold all of the possible rule types, then would use switch expressions to > ensure that all possible branches of my code were handled if the parameter > is of that sealed type. For the most part, it was a pleasant experience > where the code more or less wrote itself. > > The part that I enjoyed the most about this was the ease of refactoring > that sealed types, records, and switch expressions allowed. This project > grew in difficulty very quickly, so I found myself refactoring my solution > many times. Records automatically update all of their methods when you > realize that that record needs to/shouldn't have a field. And switch > expressions combined with sealed types ensured that if I added a new > permitted subclass, I would have to update all of my methods that used > switch expressions. That fact especially made me gravitate to using switch > expressions to get as much totality as possible. When refactoring your > code, totality is a massive time-saver and bug-preventer. Combine that with > the pre-existing fact that interfaces force all subclasses to have the > instance methods defined, and I had some powerful tools for refactoring > that allowed me to iterate very quickly. I found that to be especially > powerful because, when dealing with software that is exposed to the outside > world, making that code easy to refactor is a must. The outside world is > constantly changing, so it is important that we can change quickly too. > Therefore, I really want to congratulate you all on creating such a > powerful and expressive feature. I really enjoyed building this project, > and I'm excited to add a lot more functionality to it. > > However, while I found working with sealed types and their permitted > subclasses to be a smooth experience, I found the process of turning data > from untyped Strings into one of the permitted subclasses to be a rather > frustrating and difficult experience. > > At first glance, the solution looks really simple - just make a simple > parse method like this. > > public static MySealedType parse(String sanitizedUserInput) > { > > //if string can safely be turned into Subclass1, then store into > Subclass1 and return > //else, attempt for all other subclasses > //else, fail because string must be invalid to get here > > } > > Just like that, I should have my gateway into the world of strongly typed, > expressive data-oriented programming, right? Unfortunately, maintaining > that method got ugly fast. For starters, I don't have a small handful of > permitted subclasses, I have many of them. Currently, there are 11, but I'm > expecting my final design to have a little over 30 subclasses total. On top > of that, since my incoming strings are natural English, each of my if > branches carries non-trivial amounts of logic so that I can perform the > necessary validation against all edge cases. > > To better explain the complexity, I had created a complex regex with > capture groups for each permitted subclass, and then used that to validate > the incoming String. If the regex matches, pass the values contained in the > capture groups onto the constructor as a List, then return the > subclass containing the data of the string. > > At first, this worked well, but as the number of subclasses grew, this got > very difficult to maintain as well. This difficulty was twofold. > > Problem 1 - I found that my regex would frequently be misaligned with my > constructors during refactoring. If I decided that a record needed a new > field, or that a field should be removed, I would update the record but not > the regex, and then find errors during runtime. In fact, I sometimes didn't > find errors during runtime because List had the same number of > elements as the constructor was expecting, but the fields were not aligned > to the right index. This cost me a lot of development time. > > Problem 2 - I found that there wasn't an easy way to make sure that all of > my subclasses followed all the rules that they were supposed to, and thus, > I kept forgetting to implement those rules in one way or another every time > I refactored. For example, for problem 1, I said that every subclass must > have a regex. However, I couldn't find some compiler enforced way to > enforce this. > > * Interfaces are only able to enforce instance methods. However, I can't > have my regex be an instance method. That would be putting the cart before > the horse - I am using the regex to create an instance, so the instance > method is not helpful here > > * If I used a sealed abstract class instead and had permitted subclasses > instead of permitted records, I still couldn't store my regex as a final > instance field for the above reason. > > * In Java, static methods cannot be overrided, so I can't use a static > method on my sealed interface. The static method would belong to the > interface, not to the child subclasses. > > * And a static final field would not work for the same reason above. > > I ran into similar troubles when creating the alternative constructors for > each permitted subrecord. Almost all of the above bulleted points apply, > with the only exception being that for an abstract class, you can > *technically* force your subclasses to call the super constructor. However, > that did very little to help me solve my problem. Maybe I'm wrong and this > is the silver bullet I am looking for, but it certainly doesn't seem like > it. Therefore, I stuck to my original solution of a sealed interface with > permitted subrecords. > > But back to my original point. I had 2 problems - misalignment and no > enforcement of my abstract rules. Since I kept changing and creating and > recreating more and more subclasses, these 2 pain points became bigger and > bigger thorns in my side. Worse yet, I actually wanted to add more rules to > make these classes even easier to work with, but decided not to after > seeing the above difficulty. > > To alleviate problem 1, I stored my regexes in the records themselves, so > that I would be forced to see the regex each time I looked at the record. > For the most part, that solution seems to be good enough to deal with regex > misalignment. > > To alleviate problem 2, I decided to brute force some totality and > enforcement of my own. I fully admit, the solution I came up with here is a > bad practice and something no one should imitate, but I found this to be > the most effective way for me to enforce the rules I needed. > > I used reflection on my sealed interface. I got the sealed type class, > called Class::getPermittedSubclasses, looped through the subclasses, did an > unsafe cast from Class to Class (because > ::getPermittedSubclasses doesn't do that on its own for some reason???), > called Class::getConstructor with the parameter being List.class (to > represent the list of strings), and then used that to construct a > Map, MySealedInterface>>. I didn't do the > same for the regex because that monstrosity of code included a Map::put > which would take in the regex and the constructor. Therefore, it was pretty > easy to remember both since they were right next to each other, and JVM > will error out on startup if I forget to include my constructor. So, I have > effectively solved both of my problems, but in less than desirable ways. > > For problem 2, one analogy that kept popping into my head was the idea of > there being 2 islands. The island on the right has strong types, totality, > pattern matching, and more. Meanwhile the island on the left is where > everything is untyped and just strings. There does exist a bridge between > the 2, but it's either difficult to make, doesn't scale very well, or not > very flexible. > > This analogy really helped realize my frustration with it because it > actually showed why I like Java enums so much. You can use the same analogy > as above. The island on the right has ::ordinal, ::name, ::values, enums > having their own instance fields and methods, and even some powerful tools > like EnumSet and EnumMap. But what really ties it all together is that, > there is a very clear and defined bridge between the left and the right - > the ::valueOf method. Having this centralized pathway between the 2 made > working with enums a pleasure and something I always liked to use when > dealing with my code's interactions with the outside world. That ::valueOf > enforced a constraint against all incoming Strings. And therefore, it > allowed me to just perform some sanitizations along the way to make sure > that that method could do it's job (uppercase all strings, remove > non-identifier characters, etc). If it wasn't for JEP 301, I would call > enums perfect. > > I just wish that there was some similar centralized pathway between > data-oriented programming and the outside world. Some way for me to define > on my sealed type, a method to give me a pathway to all of the permitted > subclasses. Obviously, I can build it on my own, but that is where most of > my pain points came from. Really, having some way to enforce that all of my > subclasses have a similar class level validation logic and a similar class > level factory/constructor method is what I am missing. > > That is the summary of my thoughts. Please do not misinterpret the > extended discussion on the negatives to mean that I found the negative to > be even equal to, let alone more than, the positives. I found this to be an > overwhelmingly pleasant experience. Once I got my data turned into a type, > everything flowed perfectly. It was just difficult to get it into a type in > the first place, and it took a lot of words for me to explain why. > > Thank you all for your time and your help! > David Alayachew > -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Fri Sep 9 16:15:40 2022 From: davidalayachew at gmail.com (David Alayachew) Date: Fri, 9 Sep 2022 12:15:40 -0400 Subject: My experience with Sealed Types and Data-Oriented Programming In-Reply-To: References: Message-ID: Hello, (Sorry for resending, I forgot to include amber-dev as one of the recipients) Thank you for your response! I am having trouble visualizing what the code would look like, but I think I understand the spirit of your request. In short, it sounds like you are addressing Problem 1 by finding a way to make the regex follow the records structure. Almost as if the records body/components are the regex itself. That is a very good idea on paper, and I am trying to think of ways to put it into practice. Maybe instead of having the constructor and regex be separate, we could combine them both into a method that returns some variant of an Optional type? The method would attempt to parse the String into an object. If it succeeded, it would return a wrapped value, which we would have to use pattern matching to fetch. And if not, we can have a dedicated type to explain why it failed, or an Empty variant of optional if that's too much (though at that point, just use Optional). And the nice part of the solution is that it would respond to refactoring like you mentioned above. If I change the fields in my record, I am forced to change this method too because this method constructs the record. You can't construct a new record using old fields. That would make it easier to build and maintain. I could call the method ::of(String) and have it return that Optional-like thing I mentioned above. While I don't think this solution addresses Problem 2, we have further dealt with Problem 1. I think your suggestion is a good idea, and I will try to implement it tonight. Let me know if you think this should be done differently. Thank you for your help! David Alayachew On Fri, Sep 9, 2022 at 11:31 AM Nathan Reynolds wrote: > I am not even an amateur with parsers. It seems like you need a > backtracking parser that converts strings into records. When the parser > finds a construct that matches a record, it creates the record. The code > that links the grammar to the records needs to be linked at the Java code > level so that a change in a record breaks the grammar and vis-a-versa. I > don't of anything like this, but that doesn't say much since writing this > was pushing the boundaries of my knowledge. > > On Fri, Sep 9, 2022 at 7:41 AM David Alayachew > wrote: > >> Hello Amber Team, >> >> I just wanted to share my experiences with Sealed Types and Data-Oriented >> Programming. Specifically, I wanted to show how things turned out when I >> used them in a project. This project was built from the ground up to use >> Data-Oriented Programming to its fullest extent. If you want an in-depth >> look at the project itself, here is a link to the GitHub. If you clone it, >> you can run it right now using Java 18 + preview features. >> >> The GitHub repo = https://github.com/davidalayachew/RulesEngine >> >> Current version = >> https://github.com/davidalayachew/RulesEngine/commit/0e7fa42db4bbebaa3aa30f882645226d28e63ff4 >> >> The project I am building is essentially an Inference Engine. This is >> very similar to Prolog, where you can give the language a set of rules, and >> then the language can use those rules to make logical deductions if you ask >> it a question. The only difference is that my version accepts plain English >> sentences, as opposed to requiring you to know syntax beforehand. >> >> Here is a snippet from the logs to show how things work. >> >> David is a programmer >> -------- OK >> Every programmer is an engineer >> -------- OK >> Every engineer is an artist >> -------- OK >> Is David an artist? >> -------- CORRECT >> >> As you can see, it takes in natural English and gleans rules from it, >> then uses those rules to perform logical deductions when a query is later >> made. >> >> Sealed types made this really powerful to work with because it helped me >> ensure that I was covering every edge case. I used a sealed interface to >> hold all of the possible rule types, then would use switch expressions to >> ensure that all possible branches of my code were handled if the parameter >> is of that sealed type. For the most part, it was a pleasant experience >> where the code more or less wrote itself. >> >> The part that I enjoyed the most about this was the ease of refactoring >> that sealed types, records, and switch expressions allowed. This project >> grew in difficulty very quickly, so I found myself refactoring my solution >> many times. Records automatically update all of their methods when you >> realize that that record needs to/shouldn't have a field. And switch >> expressions combined with sealed types ensured that if I added a new >> permitted subclass, I would have to update all of my methods that used >> switch expressions. That fact especially made me gravitate to using switch >> expressions to get as much totality as possible. When refactoring your >> code, totality is a massive time-saver and bug-preventer. Combine that with >> the pre-existing fact that interfaces force all subclasses to have the >> instance methods defined, and I had some powerful tools for refactoring >> that allowed me to iterate very quickly. I found that to be especially >> powerful because, when dealing with software that is exposed to the outside >> world, making that code easy to refactor is a must. The outside world is >> constantly changing, so it is important that we can change quickly too. >> Therefore, I really want to congratulate you all on creating such a >> powerful and expressive feature. I really enjoyed building this project, >> and I'm excited to add a lot more functionality to it. >> >> However, while I found working with sealed types and their permitted >> subclasses to be a smooth experience, I found the process of turning data >> from untyped Strings into one of the permitted subclasses to be a rather >> frustrating and difficult experience. >> >> At first glance, the solution looks really simple - just make a simple >> parse method like this. >> >> public static MySealedType parse(String sanitizedUserInput) >> { >> >> //if string can safely be turned into Subclass1, then store into >> Subclass1 and return >> //else, attempt for all other subclasses >> //else, fail because string must be invalid to get here >> >> } >> >> Just like that, I should have my gateway into the world of strongly >> typed, expressive data-oriented programming, right? Unfortunately, >> maintaining that method got ugly fast. For starters, I don't have a small >> handful of permitted subclasses, I have many of them. Currently, there are >> 11, but I'm expecting my final design to have a little over 30 subclasses >> total. On top of that, since my incoming strings are natural English, each >> of my if branches carries non-trivial amounts of logic so that I can >> perform the necessary validation against all edge cases. >> >> To better explain the complexity, I had created a complex regex with >> capture groups for each permitted subclass, and then used that to validate >> the incoming String. If the regex matches, pass the values contained in the >> capture groups onto the constructor as a List, then return the >> subclass containing the data of the string. >> >> At first, this worked well, but as the number of subclasses grew, this >> got very difficult to maintain as well. This difficulty was twofold. >> >> Problem 1 - I found that my regex would frequently be misaligned with my >> constructors during refactoring. If I decided that a record needed a new >> field, or that a field should be removed, I would update the record but not >> the regex, and then find errors during runtime. In fact, I sometimes didn't >> find errors during runtime because List had the same number of >> elements as the constructor was expecting, but the fields were not aligned >> to the right index. This cost me a lot of development time. >> >> Problem 2 - I found that there wasn't an easy way to make sure that all >> of my subclasses followed all the rules that they were supposed to, and >> thus, I kept forgetting to implement those rules in one way or another >> every time I refactored. For example, for problem 1, I said that every >> subclass must have a regex. However, I couldn't find some compiler enforced >> way to enforce this. >> >> * Interfaces are only able to enforce instance methods. However, I can't >> have my regex be an instance method. That would be putting the cart before >> the horse - I am using the regex to create an instance, so the instance >> method is not helpful here >> >> * If I used a sealed abstract class instead and had permitted subclasses >> instead of permitted records, I still couldn't store my regex as a final >> instance field for the above reason. >> >> * In Java, static methods cannot be overrided, so I can't use a static >> method on my sealed interface. The static method would belong to the >> interface, not to the child subclasses. >> >> * And a static final field would not work for the same reason above. >> >> I ran into similar troubles when creating the alternative constructors >> for each permitted subrecord. Almost all of the above bulleted points >> apply, with the only exception being that for an abstract class, you can >> *technically* force your subclasses to call the super constructor. However, >> that did very little to help me solve my problem. Maybe I'm wrong and this >> is the silver bullet I am looking for, but it certainly doesn't seem like >> it. Therefore, I stuck to my original solution of a sealed interface with >> permitted subrecords. >> >> But back to my original point. I had 2 problems - misalignment and no >> enforcement of my abstract rules. Since I kept changing and creating and >> recreating more and more subclasses, these 2 pain points became bigger and >> bigger thorns in my side. Worse yet, I actually wanted to add more rules to >> make these classes even easier to work with, but decided not to after >> seeing the above difficulty. >> >> To alleviate problem 1, I stored my regexes in the records themselves, so >> that I would be forced to see the regex each time I looked at the record. >> For the most part, that solution seems to be good enough to deal with regex >> misalignment. >> >> To alleviate problem 2, I decided to brute force some totality and >> enforcement of my own. I fully admit, the solution I came up with here is a >> bad practice and something no one should imitate, but I found this to be >> the most effective way for me to enforce the rules I needed. >> >> I used reflection on my sealed interface. I got the sealed type class, >> called Class::getPermittedSubclasses, looped through the subclasses, did an >> unsafe cast from Class to Class (because >> ::getPermittedSubclasses doesn't do that on its own for some reason???), >> called Class::getConstructor with the parameter being List.class (to >> represent the list of strings), and then used that to construct a >> Map, MySealedInterface>>. I didn't do the >> same for the regex because that monstrosity of code included a Map::put >> which would take in the regex and the constructor. Therefore, it was pretty >> easy to remember both since they were right next to each other, and JVM >> will error out on startup if I forget to include my constructor. So, I have >> effectively solved both of my problems, but in less than desirable ways. >> >> For problem 2, one analogy that kept popping into my head was the idea of >> there being 2 islands. The island on the right has strong types, totality, >> pattern matching, and more. Meanwhile the island on the left is where >> everything is untyped and just strings. There does exist a bridge between >> the 2, but it's either difficult to make, doesn't scale very well, or not >> very flexible. >> >> This analogy really helped realize my frustration with it because it >> actually showed why I like Java enums so much. You can use the same analogy >> as above. The island on the right has ::ordinal, ::name, ::values, enums >> having their own instance fields and methods, and even some powerful tools >> like EnumSet and EnumMap. But what really ties it all together is that, >> there is a very clear and defined bridge between the left and the right - >> the ::valueOf method. Having this centralized pathway between the 2 made >> working with enums a pleasure and something I always liked to use when >> dealing with my code's interactions with the outside world. That ::valueOf >> enforced a constraint against all incoming Strings. And therefore, it >> allowed me to just perform some sanitizations along the way to make sure >> that that method could do it's job (uppercase all strings, remove >> non-identifier characters, etc). If it wasn't for JEP 301, I would call >> enums perfect. >> >> I just wish that there was some similar centralized pathway between >> data-oriented programming and the outside world. Some way for me to define >> on my sealed type, a method to give me a pathway to all of the permitted >> subclasses. Obviously, I can build it on my own, but that is where most of >> my pain points came from. Really, having some way to enforce that all of my >> subclasses have a similar class level validation logic and a similar class >> level factory/constructor method is what I am missing. >> >> That is the summary of my thoughts. Please do not misinterpret the >> extended discussion on the negatives to mean that I found the negative to >> be even equal to, let alone more than, the positives. I found this to be an >> overwhelmingly pleasant experience. Once I got my data turned into a type, >> everything flowed perfectly. It was just difficult to get it into a type in >> the first place, and it took a lot of words for me to explain why. >> >> Thank you all for your time and your help! >> David Alayachew >> > -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Fri Sep 9 16:38:00 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 9 Sep 2022 18:38:00 +0200 (CEST) Subject: My experience with Sealed Types and Data-Oriented Programming In-Reply-To: References: Message-ID: <61787126.2198317.1662741480629.JavaMail.zimbra@u-pem.fr> > From: "David Alayachew" > To: "amber-dev" > Sent: Friday, September 9, 2022 3:41:11 PM > Subject: My experience with Sealed Types and Data-Oriented Programming > Hello Amber Team, > I just wanted to share my experiences with Sealed Types and Data-Oriented > Programming. Specifically, I wanted to show how things turned out when I used > them in a project. This project was built from the ground up to use > Data-Oriented Programming to its fullest extent. If you want an in-depth look > at the project itself, here is a link to the GitHub. If you clone it, you can > run it right now using Java 18 + preview features. > The GitHub repo = [ https://github.com/davidalayachew/RulesEngine | > https://github.com/davidalayachew/RulesEngine ] > Current version = [ > https://github.com/davidalayachew/RulesEngine/commit/0e7fa42db4bbebaa3aa30f882645226d28e63ff4 > | > https://github.com/davidalayachew/RulesEngine/commit/0e7fa42db4bbebaa3aa30f882645226d28e63ff4 > ] > The project I am building is essentially an Inference Engine. This is very > similar to Prolog, where you can give the language a set of rules, and then the > language can use those rules to make logical deductions if you ask it a > question. The only difference is that my version accepts plain English > sentences, as opposed to requiring you to know syntax beforehand. > Here is a snippet from the logs to show how things work. > David is a programmer > -------- OK > Every programmer is an engineer > -------- OK > Every engineer is an artist > -------- OK > Is David an artist? > -------- CORRECT > As you can see, it takes in natural English and gleans rules from it, then uses > those rules to perform logical deductions when a query is later made. > Sealed types made this really powerful to work with because it helped me ensure > that I was covering every edge case. I used a sealed interface to hold all of > the possible rule types, then would use switch expressions to ensure that all > possible branches of my code were handled if the parameter is of that sealed > type. For the most part, it was a pleasant experience where the code more or > less wrote itself. > The part that I enjoyed the most about this was the ease of refactoring that > sealed types, records, and switch expressions allowed. This project grew in > difficulty very quickly, so I found myself refactoring my solution many times. > Records automatically update all of their methods when you realize that that > record needs to/shouldn't have a field. And switch expressions combined with > sealed types ensured that if I added a new permitted subclass, I would have to > update all of my methods that used switch expressions. That fact especially > made me gravitate to using switch expressions to get as much totality as > possible. When refactoring your code, totality is a massive time-saver and > bug-preventer. Combine that with the pre-existing fact that interfaces force > all subclasses to have the instance methods defined, and I had some powerful > tools for refactoring that allowed me to iterate very quickly. I found that to > be especially powerful because, when dealing with software that is exposed to > the outside world, making that code easy to refactor is a must. The outside > world is constantly changing, so it is important that we can change quickly > too. Therefore, I really want to congratulate you all on creating such a > powerful and expressive feature. I really enjoyed building this project, and > I'm excited to add a lot more functionality to it. > However, while I found working with sealed types and their permitted subclasses > to be a smooth experience, I found the process of turning data from untyped > Strings into one of the permitted subclasses to be a rather frustrating and > difficult experience. > At first glance, the solution looks really simple - just make a simple parse > method like this. > public static MySealedType parse(String sanitizedUserInput) > { > //if string can safely be turned into Subclass1, then store into Subclass1 and > return > //else, attempt for all other subclasses > //else, fail because string must be invalid to get here > } > Just like that, I should have my gateway into the world of strongly typed, > expressive data-oriented programming, right? Unfortunately, maintaining that > method got ugly fast. For starters, I don't have a small handful of permitted > subclasses, I have many of them. Currently, there are 11, but I'm expecting my > final design to have a little over 30 subclasses total. On top of that, since > my incoming strings are natural English, each of my if branches carries > non-trivial amounts of logic so that I can perform the necessary validation > against all edge cases. > To better explain the complexity, I had created a complex regex with capture > groups for each permitted subclass, and then used that to validate the incoming > String. If the regex matches, pass the values contained in the capture groups > onto the constructor as a List, then return the subclass containing the > data of the string. > At first, this worked well, but as the number of subclasses grew, this got very > difficult to maintain as well. This difficulty was twofold. > Problem 1 - I found that my regex would frequently be misaligned with my > constructors during refactoring. If I decided that a record needed a new field, > or that a field should be removed, I would update the record but not the regex, > and then find errors during runtime. In fact, I sometimes didn't find errors > during runtime because List had the same number of elements as the > constructor was expecting, but the fields were not aligned to the right index. > This cost me a lot of development time. > Problem 2 - I found that there wasn't an easy way to make sure that all of my > subclasses followed all the rules that they were supposed to, and thus, I kept > forgetting to implement those rules in one way or another every time I > refactored. For example, for problem 1, I said that every subclass must have a > regex. However, I couldn't find some compiler enforced way to enforce this. > * Interfaces are only able to enforce instance methods. However, I can't have my > regex be an instance method. That would be putting the cart before the horse - > I am using the regex to create an instance, so the instance method is not > helpful here > * If I used a sealed abstract class instead and had permitted subclasses instead > of permitted records, I still couldn't store my regex as a final instance field > for the above reason. > * In Java, static methods cannot be overrided, so I can't use a static method on > my sealed interface. The static method would belong to the interface, not to > the child subclasses. > * And a static final field would not work for the same reason above. > I ran into similar troubles when creating the alternative constructors for each > permitted subrecord. Almost all of the above bulleted points apply, with the > only exception being that for an abstract class, you can *technically* force > your subclasses to call the super constructor. However, that did very little to > help me solve my problem. Maybe I'm wrong and this is the silver bullet I am > looking for, but it certainly doesn't seem like it. Therefore, I stuck to my > original solution of a sealed interface with permitted subrecords. > But back to my original point. I had 2 problems - misalignment and no > enforcement of my abstract rules. Since I kept changing and creating and > recreating more and more subclasses, these 2 pain points became bigger and > bigger thorns in my side. Worse yet, I actually wanted to add more rules to > make these classes even easier to work with, but decided not to after seeing > the above difficulty. > To alleviate problem 1, I stored my regexes in the records themselves, so that I > would be forced to see the regex each time I looked at the record. For the most > part, that solution seems to be good enough to deal with regex misalignment. > To alleviate problem 2, I decided to brute force some totality and enforcement > of my own. I fully admit, the solution I came up with here is a bad practice > and something no one should imitate, but I found this to be the most effective > way for me to enforce the rules I needed. > I used reflection on my sealed interface. I got the sealed type class, called > Class::getPermittedSubclasses, looped through the subclasses, did an unsafe > cast from Class to Class (because ::getPermittedSubclasses > doesn't do that on its own for some reason???), called Class::getConstructor > with the parameter being List.class (to represent the list of strings), and > then used that to construct a Map, > MySealedInterface>>. I didn't do the same for the regex because that > monstrosity of code included a Map::put which would take in the regex and the > constructor. Therefore, it was pretty easy to remember both since they were > right next to each other, and JVM will error out on startup if I forget to > include my constructor. So, I have effectively solved both of my problems, but > in less than desirable ways. > For problem 2, one analogy that kept popping into my head was the idea of there > being 2 islands. The island on the right has strong types, totality, pattern > matching, and more. Meanwhile the island on the left is where everything is > untyped and just strings. There does exist a bridge between the 2, but it's > either difficult to make, doesn't scale very well, or not very flexible. > This analogy really helped realize my frustration with it because it actually > showed why I like Java enums so much. You can use the same analogy as above. > The island on the right has ::ordinal, ::name, ::values, enums having their own > instance fields and methods, and even some powerful tools like EnumSet and > EnumMap. But what really ties it all together is that, there is a very clear > and defined bridge between the left and the right - the ::valueOf method. > Having this centralized pathway between the 2 made working with enums a > pleasure and something I always liked to use when dealing with my code's > interactions with the outside world. That ::valueOf enforced a constraint > against all incoming Strings. And therefore, it allowed me to just perform some > sanitizations along the way to make sure that that method could do it's job > (uppercase all strings, remove non-identifier characters, etc). If it wasn't > for JEP 301, I would call enums perfect. > I just wish that there was some similar centralized pathway between > data-oriented programming and the outside world. Some way for me to define on > my sealed type, a method to give me a pathway to all of the permitted > subclasses. Obviously, I can build it on my own, but that is where most of my > pain points came from. Really, having some way to enforce that all of my > subclasses have a similar class level validation logic and a similar class > level factory/constructor method is what I am missing. > That is the summary of my thoughts. Please do not misinterpret the extended > discussion on the negatives to mean that I found the negative to be even equal > to, let alone more than, the positives. I found this to be an overwhelmingly > pleasant experience. Once I got my data turned into a type, everything flowed > perfectly. It was just difficult to get it into a type in the first place, and > it took a lot of words for me to explain why. The solution is known as type class (see [1] for type class in Scala 3) sadly Java does not support them (yet ?). Another solution is to switch on classes with Class parseableType = (...) Parseable.class.getPermittedSubclasses(); switch(parseableType) { case Identifier.class -> ... case Type.class -> ... // etc } Here because Parseable is sealed, the compiler knows all the classes so can do an exhaustive checks, sadly Java does not support them too. So if we can not use the compiler, the best is to write unit tests that will check that there is a field named "regex" of type pattern on all subclasses and also checks that the is a unit test for each subclasses that check the string format (by doing reflection) on the unit tests. The idea is to replace the checks you would like the compiler does for you by unit tests that ensure that everything follow the "meta-protocol" you have defined. By doing the reflection in the tests instead of in the main code avoid to make the main code slow and you can also check that the test coverage is good. > Thank you all for your time and your help! > David Alayachew regards, R?mi [1] https://docs.scala-lang.org/scala3/book/ca-type-classes.html -------------- next part -------------- An HTML attachment was scrubbed... URL: From numeralnathan at gmail.com Fri Sep 9 17:24:44 2022 From: numeralnathan at gmail.com (Nathan Reynolds) Date: Fri, 9 Sep 2022 11:24:44 -0600 Subject: My experience with Sealed Types and Data-Oriented Programming In-Reply-To: References: Message-ID: It's been a while. One parser I used allowed for writing a grammar. It then parsed the grammar and turned it into Java code. The grammar specified how to hook the generated Java code into code that hooked into the rest of the Java code. If the grammar became out of sync with the rest of the Java code, then the build process would flag errors in the grammar. Instead of "of()", use "parse()". The name is more descriptive. On Fri, Sep 9, 2022 at 10:15 AM David Alayachew wrote: > Hello, > > (Sorry for resending, I forgot to include amber-dev as one of the > recipients) > > Thank you for your response! I am having trouble visualizing what the code > would look like, but I think I understand the spirit of your request. > > In short, it sounds like you are addressing Problem 1 by finding a way to > make the regex follow the records structure. Almost as if the records > body/components are the regex itself. That is a very good idea on paper, > and I am trying to think of ways to put it into practice. > > Maybe instead of having the constructor and regex be separate, we could > combine them both into a method that returns some variant of an Optional > type? The method would attempt to parse the String into an object. If it > succeeded, it would return a wrapped value, which we would have to use > pattern matching to fetch. And if not, we can have a dedicated type to > explain why it failed, or an Empty variant of optional if that's too much > (though at that point, just use Optional). > > And the nice part of the solution is that it would respond to refactoring > like you mentioned above. If I change the fields in my record, I am forced > to change this method too because this method constructs the record. You > can't construct a new record using old fields. That would make it easier to > build and maintain. I could call the method ::of(String) and have it return > that Optional-like thing I mentioned above. > > While I don't think this solution addresses Problem 2, we have further > dealt with Problem 1. I think your suggestion is a good idea, and I will > try to implement it tonight. Let me know if you think this should be done > differently. > > Thank you for your help! > David Alayachew > > On Fri, Sep 9, 2022 at 11:31 AM Nathan Reynolds > wrote: > >> I am not even an amateur with parsers. It seems like you need a >> backtracking parser that converts strings into records. When the parser >> finds a construct that matches a record, it creates the record. The code >> that links the grammar to the records needs to be linked at the Java code >> level so that a change in a record breaks the grammar and vis-a-versa. I >> don't of anything like this, but that doesn't say much since writing this >> was pushing the boundaries of my knowledge. >> >> On Fri, Sep 9, 2022 at 7:41 AM David Alayachew >> wrote: >> >>> Hello Amber Team, >>> >>> I just wanted to share my experiences with Sealed Types and >>> Data-Oriented Programming. Specifically, I wanted to show how things turned >>> out when I used them in a project. This project was built from the ground >>> up to use Data-Oriented Programming to its fullest extent. If you want an >>> in-depth look at the project itself, here is a link to the GitHub. If you >>> clone it, you can run it right now using Java 18 + preview features. >>> >>> The GitHub repo = https://github.com/davidalayachew/RulesEngine >>> >>> Current version = >>> https://github.com/davidalayachew/RulesEngine/commit/0e7fa42db4bbebaa3aa30f882645226d28e63ff4 >>> >>> The project I am building is essentially an Inference Engine. This is >>> very similar to Prolog, where you can give the language a set of rules, and >>> then the language can use those rules to make logical deductions if you ask >>> it a question. The only difference is that my version accepts plain English >>> sentences, as opposed to requiring you to know syntax beforehand. >>> >>> Here is a snippet from the logs to show how things work. >>> >>> David is a programmer >>> -------- OK >>> Every programmer is an engineer >>> -------- OK >>> Every engineer is an artist >>> -------- OK >>> Is David an artist? >>> -------- CORRECT >>> >>> As you can see, it takes in natural English and gleans rules from it, >>> then uses those rules to perform logical deductions when a query is later >>> made. >>> >>> Sealed types made this really powerful to work with because it helped me >>> ensure that I was covering every edge case. I used a sealed interface to >>> hold all of the possible rule types, then would use switch expressions to >>> ensure that all possible branches of my code were handled if the parameter >>> is of that sealed type. For the most part, it was a pleasant experience >>> where the code more or less wrote itself. >>> >>> The part that I enjoyed the most about this was the ease of refactoring >>> that sealed types, records, and switch expressions allowed. This project >>> grew in difficulty very quickly, so I found myself refactoring my solution >>> many times. Records automatically update all of their methods when you >>> realize that that record needs to/shouldn't have a field. And switch >>> expressions combined with sealed types ensured that if I added a new >>> permitted subclass, I would have to update all of my methods that used >>> switch expressions. That fact especially made me gravitate to using switch >>> expressions to get as much totality as possible. When refactoring your >>> code, totality is a massive time-saver and bug-preventer. Combine that with >>> the pre-existing fact that interfaces force all subclasses to have the >>> instance methods defined, and I had some powerful tools for refactoring >>> that allowed me to iterate very quickly. I found that to be especially >>> powerful because, when dealing with software that is exposed to the outside >>> world, making that code easy to refactor is a must. The outside world is >>> constantly changing, so it is important that we can change quickly too. >>> Therefore, I really want to congratulate you all on creating such a >>> powerful and expressive feature. I really enjoyed building this project, >>> and I'm excited to add a lot more functionality to it. >>> >>> However, while I found working with sealed types and their permitted >>> subclasses to be a smooth experience, I found the process of turning data >>> from untyped Strings into one of the permitted subclasses to be a rather >>> frustrating and difficult experience. >>> >>> At first glance, the solution looks really simple - just make a simple >>> parse method like this. >>> >>> public static MySealedType parse(String sanitizedUserInput) >>> { >>> >>> //if string can safely be turned into Subclass1, then store into >>> Subclass1 and return >>> //else, attempt for all other subclasses >>> //else, fail because string must be invalid to get here >>> >>> } >>> >>> Just like that, I should have my gateway into the world of strongly >>> typed, expressive data-oriented programming, right? Unfortunately, >>> maintaining that method got ugly fast. For starters, I don't have a small >>> handful of permitted subclasses, I have many of them. Currently, there are >>> 11, but I'm expecting my final design to have a little over 30 subclasses >>> total. On top of that, since my incoming strings are natural English, each >>> of my if branches carries non-trivial amounts of logic so that I can >>> perform the necessary validation against all edge cases. >>> >>> To better explain the complexity, I had created a complex regex with >>> capture groups for each permitted subclass, and then used that to validate >>> the incoming String. If the regex matches, pass the values contained in the >>> capture groups onto the constructor as a List, then return the >>> subclass containing the data of the string. >>> >>> At first, this worked well, but as the number of subclasses grew, this >>> got very difficult to maintain as well. This difficulty was twofold. >>> >>> Problem 1 - I found that my regex would frequently be misaligned with my >>> constructors during refactoring. If I decided that a record needed a new >>> field, or that a field should be removed, I would update the record but not >>> the regex, and then find errors during runtime. In fact, I sometimes didn't >>> find errors during runtime because List had the same number of >>> elements as the constructor was expecting, but the fields were not aligned >>> to the right index. This cost me a lot of development time. >>> >>> Problem 2 - I found that there wasn't an easy way to make sure that all >>> of my subclasses followed all the rules that they were supposed to, and >>> thus, I kept forgetting to implement those rules in one way or another >>> every time I refactored. For example, for problem 1, I said that every >>> subclass must have a regex. However, I couldn't find some compiler enforced >>> way to enforce this. >>> >>> * Interfaces are only able to enforce instance methods. However, I can't >>> have my regex be an instance method. That would be putting the cart before >>> the horse - I am using the regex to create an instance, so the instance >>> method is not helpful here >>> >>> * If I used a sealed abstract class instead and had permitted subclasses >>> instead of permitted records, I still couldn't store my regex as a final >>> instance field for the above reason. >>> >>> * In Java, static methods cannot be overrided, so I can't use a static >>> method on my sealed interface. The static method would belong to the >>> interface, not to the child subclasses. >>> >>> * And a static final field would not work for the same reason above. >>> >>> I ran into similar troubles when creating the alternative constructors >>> for each permitted subrecord. Almost all of the above bulleted points >>> apply, with the only exception being that for an abstract class, you can >>> *technically* force your subclasses to call the super constructor. However, >>> that did very little to help me solve my problem. Maybe I'm wrong and this >>> is the silver bullet I am looking for, but it certainly doesn't seem like >>> it. Therefore, I stuck to my original solution of a sealed interface with >>> permitted subrecords. >>> >>> But back to my original point. I had 2 problems - misalignment and no >>> enforcement of my abstract rules. Since I kept changing and creating and >>> recreating more and more subclasses, these 2 pain points became bigger and >>> bigger thorns in my side. Worse yet, I actually wanted to add more rules to >>> make these classes even easier to work with, but decided not to after >>> seeing the above difficulty. >>> >>> To alleviate problem 1, I stored my regexes in the records themselves, >>> so that I would be forced to see the regex each time I looked at the >>> record. For the most part, that solution seems to be good enough to deal >>> with regex misalignment. >>> >>> To alleviate problem 2, I decided to brute force some totality and >>> enforcement of my own. I fully admit, the solution I came up with here is a >>> bad practice and something no one should imitate, but I found this to be >>> the most effective way for me to enforce the rules I needed. >>> >>> I used reflection on my sealed interface. I got the sealed type class, >>> called Class::getPermittedSubclasses, looped through the subclasses, did an >>> unsafe cast from Class to Class (because >>> ::getPermittedSubclasses doesn't do that on its own for some reason???), >>> called Class::getConstructor with the parameter being List.class (to >>> represent the list of strings), and then used that to construct a >>> Map, MySealedInterface>>. I didn't do the >>> same for the regex because that monstrosity of code included a Map::put >>> which would take in the regex and the constructor. Therefore, it was pretty >>> easy to remember both since they were right next to each other, and JVM >>> will error out on startup if I forget to include my constructor. So, I have >>> effectively solved both of my problems, but in less than desirable ways. >>> >>> For problem 2, one analogy that kept popping into my head was the idea >>> of there being 2 islands. The island on the right has strong types, >>> totality, pattern matching, and more. Meanwhile the island on the left is >>> where everything is untyped and just strings. There does exist a bridge >>> between the 2, but it's either difficult to make, doesn't scale very well, >>> or not very flexible. >>> >>> This analogy really helped realize my frustration with it because it >>> actually showed why I like Java enums so much. You can use the same analogy >>> as above. The island on the right has ::ordinal, ::name, ::values, enums >>> having their own instance fields and methods, and even some powerful tools >>> like EnumSet and EnumMap. But what really ties it all together is that, >>> there is a very clear and defined bridge between the left and the right - >>> the ::valueOf method. Having this centralized pathway between the 2 made >>> working with enums a pleasure and something I always liked to use when >>> dealing with my code's interactions with the outside world. That ::valueOf >>> enforced a constraint against all incoming Strings. And therefore, it >>> allowed me to just perform some sanitizations along the way to make sure >>> that that method could do it's job (uppercase all strings, remove >>> non-identifier characters, etc). If it wasn't for JEP 301, I would call >>> enums perfect. >>> >>> I just wish that there was some similar centralized pathway between >>> data-oriented programming and the outside world. Some way for me to define >>> on my sealed type, a method to give me a pathway to all of the permitted >>> subclasses. Obviously, I can build it on my own, but that is where most of >>> my pain points came from. Really, having some way to enforce that all of my >>> subclasses have a similar class level validation logic and a similar class >>> level factory/constructor method is what I am missing. >>> >>> That is the summary of my thoughts. Please do not misinterpret the >>> extended discussion on the negatives to mean that I found the negative to >>> be even equal to, let alone more than, the positives. I found this to be an >>> overwhelmingly pleasant experience. Once I got my data turned into a type, >>> everything flowed perfectly. It was just difficult to get it into a type in >>> the first place, and it took a lot of words for me to explain why. >>> >>> Thank you all for your time and your help! >>> David Alayachew >>> >> -------------- next part -------------- An HTML attachment was scrubbed... URL: From john.r.rose at oracle.com Fri Sep 9 18:46:12 2022 From: john.r.rose at oracle.com (John Rose) Date: Fri, 09 Sep 2022 11:46:12 -0700 Subject: My experience with Sealed Types and Data-Oriented Programming In-Reply-To: References: Message-ID: <4C8D0AC6-A091-4E4A-B122-595591C0B164@oracle.com> On 9 Sep 2022, at 6:41, David Alayachew wrote: > Hello Amber Team, > > I just wanted to share my experiences with Sealed Types and > Data-Oriented > Programming. Thank you for the well-written note, and for sharing your results. > ? > I used reflection on my sealed interface. I got the sealed type class, > called Class::getPermittedSubclasses, looped through the subclasses, > did an > unsafe cast from Class to Class (because > ::getPermittedSubclasses doesn't do that on its own for some > reason???), Good comment. This feels like it *might* be a simple oversight. If so we might choose to fix it. Or it might well be a conscious decision to limit the impact of reflection on the language proper. The sharpening of `String.class` to `Class` actually requires complicated moves in the heart of the language, the JLS; such moves are very expensive. Addressing this one would require JLS surgery. Sometimes inconvenient stuff like this, like the no-go for generic enums JEP 301, is due to highly technical roadblocks which are too hard to remove. > ? > I just wish that there was some similar centralized pathway between > data-oriented programming and the outside world. Some way for me to > define > on my sealed type, a method to give me a pathway to all of the > permitted > subclasses. Obviously, I can build it on my own, but that is where > most of > my pain points came from. Really, having some way to enforce that all > of my > subclasses have a similar class level validation logic and a similar > class > level factory/constructor method is what I am missing. Good summary of a tricky design problem. The images of ?islands? is good. We are also talking about a functor between categories, for those who find such ideas illuminating. One thing I would try: Attach grammar fragments or regexps-with-groups to each record using annotations. Then build a tool which uses those annotations to weave a bridge between the islands. It would require some sort of grammar generator. I?d look for a combinator-based library, I think. I don?t work in this area enough to know what?s current. But I admire some Lua-based experiments [1] and think it?s likely some Java programmer has adapted those ideas to Java and lambdas and records etc. If not maybe it?s time to do so. Thanks! ? John [1]: https://github.com/vsbenas/parser-gen -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Sun Sep 11 03:22:56 2022 From: davidalayachew at gmail.com (David Alayachew) Date: Sat, 10 Sep 2022 23:22:56 -0400 Subject: My experience with Sealed Types and Data-Oriented Programming In-Reply-To: <61787126.2198317.1662741480629.JavaMail.zimbra@u-pem.fr> References: <61787126.2198317.1662741480629.JavaMail.zimbra@u-pem.fr> Message-ID: Hello R?mi, > The solution is known as type class (see [1] for type class in Scala 3) sadly Java does not support them (yet ?). > [1] https://docs.scala-lang.org/scala3/book/ca-type-classes.html Thank you for introducing me to Scala 3 Type classes. The concept sounds very similar to interfaces with unimplemented methods, but I assume that they don't just restrict this to instance methods like Java does? > Another solution is to switch on classes with > > Class parseableType = (...) Parseable.class.getPermittedSubclasses(); > switch(parseableType) { > case Identifier.class -> ... > case Type.class -> ... > // etc > } > > Here because Parseable is sealed, the compiler knows all the classes so can do an exhaustive checks, sadly Java does not support them too. I actually thought of this too myself. I was very disappointed to discover that it's not possible. > So if we can not use the compiler, the best is to write unit tests that will check that there is a field named "regex" of type pattern on all subclasses and also checks that the is a unit test for each subclasses that check the string format (by doing reflection) on the unit tests. The idea is to replace the checks you would like the compiler does for you by unit tests that ensure that everything follow the "meta-protocol" you have defined. > > By doing the reflection in the tests instead of in the main code avoid to make the main code slow and you can also check that the test coverage is good. I definitely agree that it will improve performance. For this project, I think I will leave the reflection in the code for now, but I agree that any future projects that force me to use reflection to enforce a standard should have all reflection limited to unit tests. That said, I am still bothered by the fact that I am forced to depend on reflection. Maybe I am wrong for feeling this way, but reflection feels like using a second-class solution to solve a first-class problem. And I say second-class because it feels like I am throwing out type-safety at so many points, forcing me to operate on assumptions instead. For example, when calling the Class::getConstructor, I have to pass in a parameter of List.class to represent my List constructors. Java doesn't allow me to pass in List.class (understandably). But if I later refactor my code to take in a List instead, this method will still compile just fine, and won't fail until I try to use it during runtime. That would put me right back into the same pit that I used reflection to get myself out of. In order for me to get totality/exhaustiveness, I must give up type-safety. It feels like a bad trade. I actually ran into this exact problem during one of my refactors. I managed to get around this by using even more reflection combined with some hard coded dummy values, but that feels even worse. I wish the language gave me some way to test my parameter against all of the permitted subclasses, see which one succeeds, then return the match (ideally as a newly created instance), all without giving up the type-safety and totality that I've had up until that point. Apologies for repeating myself, I am only using this to demonstrate what I feel a first class solution would look like. Thank you so much for your response and insight! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Sun Sep 11 03:52:20 2022 From: davidalayachew at gmail.com (David Alayachew) Date: Sat, 10 Sep 2022 23:52:20 -0400 Subject: My experience with Sealed Types and Data-Oriented Programming In-Reply-To: References: Message-ID: Hello Nathan, > It's been a while. One parser I used allowed for writing a grammar. It then parsed the grammar and turned it into Java code. The grammar specified how to hook the generated Java code into code that hooked into the rest of the Java code. If the grammar became out of sync with the rest of the Java code, then the build process would flag errors in the grammar. What a powerful solution. And I definitely see where you are coming from now. Your solution goes in a completely different direction - rather than trying to bend Java into 2 directions at once (being both genericized and able to handle dynamically created rules), you have instead made Java the landing point. You just create rules, and then hardcode them into plain java code. > Instead of "of()", use "parse()". The name is more descriptive Agreed. I gave your original suggestion (or rather, my interpretation of it) a shot, and I have to say I quite liked the results! I took one of my permitted subclasses, and refactored it to no longer have an alternative constructor, but to instead, use the new ::parse method. It was nice because then I could keep all of the regex logic inside of the record and not expose it to the outside world needlessly. Furthermore, it made cycling through all of the classes to find a viable match much easier. Instead of having a Map, Parseable>>, I instead have a Set>. Thank you again for all of your help and guidance! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From numeralnathan at gmail.com Sun Sep 11 04:04:54 2022 From: numeralnathan at gmail.com (Nathan Reynolds) Date: Sat, 10 Sep 2022 23:04:54 -0500 Subject: My experience with Sealed Types and Data-Oriented Programming In-Reply-To: References: Message-ID: I'm glad it worked so well. I was dubious since you are parsing English. In general, I like to keep the parsing logic in the class of the resulting object. This way each class deals with the specifics for that class. The downside is that some logic may be duplicated. On Sat, Sep 10, 2022, 10:52 PM David Alayachew wrote: > Hello Nathan, > > > It's been a while. One parser I used allowed for writing a grammar. It > then parsed the grammar and turned it into Java code. The grammar specified > how to hook the generated Java code into code that hooked into the rest of > the Java code. If the grammar became out of sync with the rest of the Java > code, then the build process would flag errors in the grammar. > > What a powerful solution. And I definitely see where you are coming from > now. Your solution goes in a completely different direction - rather than > trying to bend Java into 2 directions at once (being both genericized and > able to handle dynamically created rules), you have instead made Java the > landing point. You just create rules, and then hardcode them into plain > java code. > > > Instead of "of()", use "parse()". The name is more descriptive > > Agreed. > > I gave your original suggestion (or rather, my interpretation of it) a > shot, and I have to say I quite liked the results! I took one of my > permitted subclasses, and refactored it to no longer have an alternative > constructor, but to instead, use the new ::parse method. It was nice > because then I could keep all of the regex logic inside of the record and > not expose it to the outside world needlessly. Furthermore, it made cycling > through all of the classes to find a viable match much easier. Instead of > having a Map, Parseable>>, I instead have a > Set>. > > Thank you again for all of your help and guidance! > David Alayachew > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Sun Sep 11 04:31:05 2022 From: davidalayachew at gmail.com (David Alayachew) Date: Sun, 11 Sep 2022 00:31:05 -0400 Subject: My experience with Sealed Types and Data-Oriented Programming In-Reply-To: <4C8D0AC6-A091-4E4A-B122-595591C0B164@oracle.com> References: <4C8D0AC6-A091-4E4A-B122-595591C0B164@oracle.com> Message-ID: Hello John, > Thank you for the well-written note, and for sharing your results. Anytime. I remember watching a video that included Brian Goetz/Ron Pressler. They mentioned that the most useful and helpful form of feedback we could provide the Java Team is a run through of us using the preview features in a non-trivial project, and noting our experiences with it. > Good comment. > > This feels like it *might* be a simple oversight. If so we might choose to fix it. Or it might well be a conscious decision to limit the impact of reflection on the language proper. The sharpening of String.class to Class actually requires complicated moves in the heart of the language, the JLS; such moves are very expensive. Addressing this one would require JLS surgery Thank you for the context. I definitely didn't realize the potential cost of making that cast. It makes a lot more sense why they might have avoided doing it. Also good to know about the Java Language Specification. I definitely see what you mean now. > Sometimes inconvenient stuff like this, like the no-go for generic enums JEP 301, is due to highly technical roadblocks which are too hard to remove. That's definitely fair. Admittedly, I was coming from a place of annoyance when I was talking about the cast and also JEP 301. I need to be more mindful of the level of complexity and collaboration involved for problems like that. > Good summary of a tricky design problem. The images of ?islands? is good. We are also talking about a functor between categories, for those who find such ideas illuminating. I just looked up functors, and I'm having a lot of fun with the concepts behind it. Here's one example from Wikipedia. > "Let *C* and *D* be categories. A *functor* *F* from *C* to *D* is a mapping that associates each object [image: X] in *C* to an object [image: F(X)] in *D"* On paper, it seems basic and simple, but as a language feature between 2 separate categories of language citizens, that is earth-shattering. It's also hilarious because that is exactly what I tried to create through reflection. I tried to create a Map where each key is one of the permitted subclasses, and each value is the respective constructor for the permitted subclass. I had to complicate the Map a bit because of implementation details, but according to the definition above, I think I made a functor. I will have to read up on it more. > One thing I would try: Attach grammar fragments or regexps-with-groups to each record using annotations. Then build a tool which uses those annotations to weave a bridge between the islands. It would require some sort of grammar generator. I?d look for a combinator-based library, I think. I don?t work in this area enough to know what?s current. But I admire some Lua-based experiments 1 and think it?s likely some Java programmer has adapted those ideas to Java and lambdas and records etc. If not maybe it?s time to do so I'll give it a shot. I don't know about any grammar generator libraries, but one other person on this thread mentioned something similar that they built, so I am sure I'll find something. Thank you again for your help and insight! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From davidalayachew at gmail.com Sun Sep 11 04:41:02 2022 From: davidalayachew at gmail.com (David Alayachew) Date: Sun, 11 Sep 2022 00:41:02 -0400 Subject: My experience with Sealed Types and Data-Oriented Programming In-Reply-To: References: Message-ID: Hello Nathan, > I'm glad it worked so well. I was dubious since you are parsing English. Thank you very much. Yes, spoken languages carry so many more edge cases than a programming/syntactic language. That's partly why I applauded records and their maintainability. The main reason I kept refactoring my code so much was to adapt to an edge case that I had forgotten/not thought of. > In general, I like to keep the parsing logic in the class of the resulting object. This way each class deals with the specifics for that class. The downside is that some logic may be duplicated. Agreed. And yes, some duplication was involved, but frankly, it's a small cost in comparison to the simplified flow. Now if only I could turn this into some mapping that I could fetch from the sealed type. Someone else on this thread suggested using annotations to serve as my bridge between worlds. Let's see if that can serve as my mapping. Thank you for your help! David Alayachew -------------- next part -------------- An HTML attachment was scrubbed... URL: From mark.reinhold at oracle.com Tue Sep 20 18:10:20 2022 From: mark.reinhold at oracle.com (Mark Reinhold) Date: Tue, 20 Sep 2022 18:10:20 +0000 Subject: New candidate JEP: 430: String Templates (Preview) Message-ID: <20220920181019.B7D2D543703@eggemoggin.niobe.net> https://openjdk.org/jeps/430 Summary: Enhance the Java programming language with string templates, which are similar to string literals but contain embedded expressions. A string template is interpreted at run time by replacing each expression with the result of evaluating that expression, possibly after further validation and transformation. This is a preview language feature and API. - Mark From forax at univ-mlv.fr Tue Sep 20 19:47:49 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 20 Sep 2022 21:47:49 +0200 (CEST) Subject: New candidate JEP: 430: String Templates (Preview) In-Reply-To: <20220920181019.B7D2D543703@eggemoggin.niobe.net> References: <20220920181019.B7D2D543703@eggemoggin.niobe.net> Message-ID: <1351376013.9457131.1663703269571.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "mark reinhold" > Cc: "amber-dev" , "jdk-dev" > Sent: Tuesday, September 20, 2022 8:10:20 PM > Subject: New candidate JEP: 430: String Templates (Preview) > https://openjdk.org/jeps/430 > > Summary: Enhance the Java programming language with string > templates, which are similar to string literals but contain embedded > expressions. A string template is interpreted at run time by replacing > each expression with the result of evaluating that expression, possibly > after further validation and transformation. This is a preview language > feature and API. Hi all. My main concern is that the TemplatedString object erase the types, but apart trivial template processors, those types is either required or can be used in order to be more efficient. By example, for a JSON template processor, if it can know that a template value is of type String, then it knows that it has to escape it but if its an int, this is not necessary. With the current proposal, because the type is lost, each value has to be dynamically checked against all the possible classes, something that is not efficient. Otherwise the spec is not clear how STR or FMT are introduced in the scope, it is magical ? does a static import is used ? > > - Mark R?mi From brian.goetz at oracle.com Tue Sep 20 20:11:16 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 20 Sep 2022 16:11:16 -0400 Subject: New candidate JEP: 430: String Templates (Preview) In-Reply-To: <1351376013.9457131.1663703269571.JavaMail.zimbra@u-pem.fr> References: <20220920181019.B7D2D543703@eggemoggin.niobe.net> <1351376013.9457131.1663703269571.JavaMail.zimbra@u-pem.fr> Message-ID: > Hi all. > My main concern is that the TemplatedString object erase the types, but apart trivial template processors, those types is either required or can be used in order to be more efficient. While the types may be erased to Object[], the dynamic types are available to template processors.? So processors that "require" types have no problem, they just have to use `instanceof` and similar.? As to performance, yes, we would like a path to making user-written processors as efficient as we are able to make the JDK ones, but we've chosen to come back for that in a later round if need be.? The semantics are in place; we can make it more efficient later.? Java developers have waited a long time already for this functionality, and I don't want to delay it further just so we can make the advanced cases more efficient. From nathan.h.walker at gmail.com Wed Sep 21 01:57:56 2022 From: nathan.h.walker at gmail.com (Nathan Walker) Date: Tue, 20 Sep 2022 21:57:56 -0400 Subject: TemplatedString.builder() produces mutable TemplateString? Message-ID: Hey folks, I was poking around at the source in the templated-string branch and saw something that seemed odd. In the TemplatedString.TemplateBuilder.build() method there is: /** * Returns a {@link TemplatedString} based on the current state of the * {@link TemplateBuilder Builder's} fragments and values. * * @return a new TemplatedString */ public TemplatedString build() { return new SimpleTemplatedString(Collections.unmodifiableList(fragments), Collections.unmodifiableList(values)); } And SimpleTemplatedString is just: record SimpleTemplatedString(List fragments, List values) implements TemplatedString {} There doesn't seem to be any defensive copying of the fragments and values lists, just wrapping in the unmodifiableList() call. So it is unmodifiable but if a builder is reused I then I believe those lists could change. var builder = TemplatedString.builder(); TemplatedString ts = builder .fragment("a:").value(1) .fragment(", b:").value(2) .build(); String result = STR.apply(ts); // result = "a:1, b:2" builder.fragment(", c:").value(3); result = STR.apply(ts); // result = "a:1, b:2, c:3" - same TempaltedString, very different result Is this behavior intended? Based on the documentation on TemplatedString I wouldn't have thought so, but perhaps I am missing something. Thanks for your time. -------------- next part -------------- An HTML attachment was scrubbed... URL: From james.laskey at oracle.com Wed Sep 21 02:13:35 2022 From: james.laskey at oracle.com (Jim Laskey) Date: Wed, 21 Sep 2022 02:13:35 +0000 Subject: TemplatedString.builder() produces mutable TemplateString? In-Reply-To: References: Message-ID: <1793A806-2889-4EDA-B3A6-7EE97219E249@oracle.com> Thank you for reporting. I?ll check into this. Cheers, ? Jim ? On Sep 20, 2022, at 10:58 PM, Nathan Walker wrote: ? Hey folks, I was poking around at the source in the templated-string branch and saw something that seemed odd. In the TemplatedString.TemplateBuilder.build() method there is: /** * Returns a {@link TemplatedString} based on the current state of the * {@link TemplateBuilder Builder's} fragments and values. * * @return a new TemplatedString */ public TemplatedString build() { return new SimpleTemplatedString(Collections.unmodifiableList(fragments), Collections.unmodifiableList(values)); } And SimpleTemplatedString is just: record SimpleTemplatedString(List fragments, List values) implements TemplatedString {} There doesn't seem to be any defensive copying of the fragments and values lists, just wrapping in the unmodifiableList() call. So it is unmodifiable but if a builder is reused I then I believe those lists could change. var builder = TemplatedString.builder(); TemplatedString ts = builder .fragment("a:").value(1) .fragment(", b:").value(2) .build(); String result = STR.apply(ts); // result = "a:1, b:2" builder.fragment(", c:").value(3); result = STR.apply(ts); // result = "a:1, b:2, c:3" - same TempaltedString, very different result Is this behavior intended? Based on the documentation on TemplatedString I wouldn't have thought so, but perhaps I am missing something. Thanks for your time. -------------- next part -------------- An HTML attachment was scrubbed... URL: From james.laskey at oracle.com Wed Sep 21 12:03:08 2022 From: james.laskey at oracle.com (Jim Laskey) Date: Wed, 21 Sep 2022 12:03:08 +0000 Subject: TemplatedString.builder() produces mutable TemplateString? In-Reply-To: <1793A806-2889-4EDA-B3A6-7EE97219E249@oracle.com> References: <1793A806-2889-4EDA-B3A6-7EE97219E249@oracle.com> Message-ID: <18B41D38-08A0-4291-949C-296AD44800A5@oracle.com> Pushed change: /** * Returns a {@link TemplatedString} based on the current state of the * {@link TemplateBuilder Builder's} fragments and values. * * @return a new TemplatedString */ public TemplatedString build() { return TemplatedString.of(fragments, values); } On Sep 20, 2022, at 11:13 PM, Jim Laskey > wrote: Thank you for reporting. I?ll check into this. Cheers, ? Jim ? On Sep 20, 2022, at 10:58 PM, Nathan Walker > wrote: ? Hey folks, I was poking around at the source in the templated-string branch and saw something that seemed odd. In the TemplatedString.TemplateBuilder.build() method there is: /** * Returns a {@link TemplatedString} based on the current state of the * {@link TemplateBuilder Builder's} fragments and values. * * @return a new TemplatedString */ public TemplatedString build() { return new SimpleTemplatedString(Collections.unmodifiableList(fragments), Collections.unmodifiableList(values)); } And SimpleTemplatedString is just: record SimpleTemplatedString(List fragments, List values) implements TemplatedString {} There doesn't seem to be any defensive copying of the fragments and values lists, just wrapping in the unmodifiableList() call. So it is unmodifiable but if a builder is reused I then I believe those lists could change. var builder = TemplatedString.builder(); TemplatedString ts = builder .fragment("a:").value(1) .fragment(", b:").value(2) .build(); String result = STR.apply(ts); // result = "a:1, b:2" builder.fragment(", c:").value(3); result = STR.apply(ts); // result = "a:1, b:2, c:3" - same TempaltedString, very different result Is this behavior intended? Based on the documentation on TemplatedString I wouldn't have thought so, but perhaps I am missing something. Thanks for your time. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Wed Sep 21 12:55:21 2022 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 21 Sep 2022 14:55:21 +0200 (CEST) Subject: New candidate JEP: 430: String Templates (Preview) In-Reply-To: References: <20220920181019.B7D2D543703@eggemoggin.niobe.net> <1351376013.9457131.1663703269571.JavaMail.zimbra@u-pem.fr> Message-ID: <2089223013.9906788.1663764921675.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Brian Goetz" > To: "Remi Forax" , "amber-dev" > Cc: "jdk-dev" > Sent: Tuesday, September 20, 2022 10:11:16 PM > Subject: Re: New candidate JEP: 430: String Templates (Preview) >> Hi all. >> My main concern is that the TemplatedString object erase the types, but apart >> trivial template processors, those types is either required or can be used in >> order to be more efficient. > > While the types may be erased to Object[], the dynamic types are > available to template processors.? So processors that "require" types > have no problem, they just have to use `instanceof` and similar.? As to > performance, yes, we would like a path to making user-written processors > as efficient as we are able to make the JDK ones, but we've chosen to > come back for that in a later round if need be.? The semantics are in > place; we can make it more efficient later.? Java developers have waited > a long time already for this functionality, and I don't want to delay it > further just so we can make the advanced cases more efficient. It's not just about efficiency, it's about loosing type informations. Imagine that the we have a SQL update processor that create the BD table if it does not exist when updating it, because the type are erased, the information of the types of the table columns is lost. R?mi From jens.lidestrom at fripost.org Sat Sep 17 06:22:58 2022 From: jens.lidestrom at fripost.org (=?UTF-8?Q?Jens_Lidestr=c3=b6m?=) Date: Sat, 17 Sep 2022 08:22:58 +0200 Subject: My experience with Sealed Types and Data-Oriented Programming In-Reply-To: References: <4C8D0AC6-A091-4E4A-B122-595591C0B164@oracle.com> Message-ID: On 2022-09-09 20:46, John Rose wrote: > This feels like it /might/ be a simple oversight. If so we might choose to fix it. Or it might well be a conscious decision to limit the impact of reflection on the language proper. The sharpening of |String.class| to |Class| actually requires complicated moves in the heart of the language, the JLS; such moves are very expensive. Addressing this one would require JLS surgery. I don't think this applies to Class#getPermittedSubclasses. I think that method could just propagate the type parameter of Class. The magic of String.class is not needed. The cast in David Alayachew's code could simple be moved into getPermittedSubclasses, and the result type changed: public Class[] getPermittedSubclasses() { ... return (Class[]) subClasses; } Best regards, Jens Lidestr?m From mark at lawinegevaar.nl Wed Sep 21 13:51:54 2022 From: mark at lawinegevaar.nl (Mark Rotteveel) Date: Wed, 21 Sep 2022 15:51:54 +0200 Subject: Fwd: Re: New candidate JEP: 430: String Templates (Preview) In-Reply-To: <9a388828-02b3-acdc-d990-3c98598c8df5@lawinegevaar.nl> References: <9a388828-02b3-acdc-d990-3c98598c8df5@lawinegevaar.nl> Message-ID: <42bf3029-ab76-c6ed-4307-e93f926004a2@lawinegevaar.nl> I guess I should have sent this to amber-dev instead. -------------- next part -------------- An embedded message was scrubbed... From: Mark Rotteveel Subject: Re: New candidate JEP: 430: String Templates (Preview) Date: Wed, 21 Sep 2022 15:50:59 +0200 Size: 1760 URL: From attila.kelemen85 at gmail.com Wed Sep 21 21:31:06 2022 From: attila.kelemen85 at gmail.com (Attila Kelemen) Date: Wed, 21 Sep 2022 23:31:06 +0200 Subject: New candidate JEP: 430: String Templates (Preview) In-Reply-To: <20220920181019.B7D2D543703@eggemoggin.niobe.net> References: <20220920181019.B7D2D543703@eggemoggin.niobe.net> Message-ID: Hi, Though I like the general idea behind this JEP. I have some concerns with it in its current form: 1. Might be a personal preference, but I find the `TemplateProcessorExpression . Template` syntax bizarre. Also, it seems odd that the Java language doesn't care too much about terseness, but in this very-very special case, it uses up syntax possibilities (which in general could hinder future more general syntax shortening). 2. The `TemplatedString` is more complicated than necessary. It is not obvious by just looking at it, how to implement a `TemplateProcessor`. You have to carefully read its documentation to realize that you have to alternate between two iterators. To allow that, it even (potentially) needs the 'hack" of empty guard strings. Aside from looking a bit sketchy, it even makes it inconvenient to implement a `TemplateProcessor`. In my opinion, `TemplatedString` should effectively be a list of `TemplatePart` (of course, it still should exist as a wrapping abstraction), where `TemplatePart` is a sealed interface with 2 implementations (the implementations are being as obvious). This would make implementing `TemplateProcessor` considerably easier (especially with switch on types). I'm not a compiler writer (I'm sure they will hate me for this), but it looks a lot more plausible that this is more optimizable than alternating between iterators. 3. It is needless to waste (see above) a perfectly good unicode character, just to allow a find/replace. And yes, I'm heating with the spacebar. 4. Last time I saw, the compiler generated a new class for each `TemplatedString` instance, which looks quite wasteful (maybe it is to avoid copying the most likely very short list?). Note that, if it was effectively a `TemplatePart[]`, then - in some cases -, the compiler could even skip creating the `TemplatedString` To be fair, I don't find this optimization possibility that likely, because it would require some special understanding from the JIT, or the `TemplateProcessor` would require minor hacks. Attila Mark Reinhold ezt ?rta (id?pont: 2022. szept. 20., K, 20:10): > > https://openjdk.org/jeps/430 > > Summary: Enhance the Java programming language with string > templates, which are similar to string literals but contain embedded > expressions. A string template is interpreted at run time by replacing > each expression with the result of evaluating that expression, possibly > after further validation and transformation. This is a preview language > feature and API. > > - Mark From brian.goetz at oracle.com Wed Sep 21 22:35:20 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 21 Sep 2022 18:35:20 -0400 Subject: New candidate JEP: 430: String Templates (Preview) In-Reply-To: References: <20220920181019.B7D2D543703@eggemoggin.niobe.net> Message-ID: <3e8ed816-aa51-85a3-cd71-472b8e5a0bc6@oracle.com> Thanks for the thoughts.? Some comments inline. > Though I like the general idea behind this JEP. I have some concerns > with it in its current form: > > 1. Might be a personal preference, but I find the > `TemplateProcessorExpression . Template` syntax bizarre. We knew that at least one out of the 10M Java developers would loudly proclaim "I hate the syntax", so congratulations, it's you :)?? New syntaxes are risky; people are used to "${foo}" in other languages, and this is new and different, and sometimes different is scary.? But, it wasn't picked out of a hat; a good deal of thought has gone into this, and so while it may seem weird now, give it a year and I suspect it will not seem so odd.? (In any case, amber-dev isn't the forum for syntax discussions, so let's leave this here.) > 2. The `TemplatedString` is more complicated than necessary. It is not > obvious by just looking at it, how to > implement a `TemplateProcessor`. Perhaps, but the ratio of people who *use* TemplateProcessor to those who *write* it is probably 1,000 to one or more.? And there are a lot of hidden requirements that have been sussed out as a result of writing not only the obvious processors, but some less common ones as well.? So what seems unnecessary to you may simply be that you are thinking of simpler use cases. > 4. Last time I saw, the compiler generated a new class for each > `TemplatedString` instance You are aware there is a class generated per lambda / mref too, right??? And yet no one is saying "Lambda translation is too heavy."? (There are also options available to improve the translation, but it's still early for that game.) From brian.goetz at oracle.com Thu Sep 22 03:02:01 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 21 Sep 2022 23:02:01 -0400 Subject: New candidate JEP: 430: String Templates (Preview) In-Reply-To: References: <20220920181019.B7D2D543703@eggemoggin.niobe.net> <3e8ed816-aa51-85a3-cd71-472b8e5a0bc6@oracle.com> Message-ID: <0c8f978e-9295-3fae-d428-3dbd829befeb@oracle.com> > Just curious, doesn?t JSP/JSF processing already use a ?string > template? type paradigm using a ${} type syntax. ?Would any changes > here be usable either or or there? You're thinking of "EL" (expression language).? This works by using a Map-like context for key-values pairs, but the expressions are encoded as strings: "${foo.bar.baz}" where it looks in the context for "foo", then looks for a bar() / getBar() method or bar field, etc.? String templates would have given us a better way to express such things, that aren't quite so stringly typed. > Given recent log4j security issues, is there any possible risk that > expansion could introduce some exploitable logic? ?Does any sort of > constraint or mechanism need to protect against that or am I I er > thinking it? That would be a function of the template processor.? One could certainly write a template processor that was vulnerable in the same way Log4j was. > > > > Get Outlook for iOS > > ------------------------------------------------------------------------ > *From:* jdk-dev on behalf of Attila Kelemen > > *Sent:* Wednesday, September 21, 2022 6:20 PM > *To:* Brian Goetz > *Cc:* amber-dev at openjdk.org ; > jdk-dev at openjdk.org > *Subject:* Re: New candidate JEP: 430: String Templates (Preview) > Thanks for the responses. See some of my clarifications below. > > > > > > > 1. Might be a personal preference, but I find the > > > `TemplateProcessorExpression . Template` syntax bizarre. > > > > We knew that at least one out of the 10M Java developers would loudly > > proclaim "I hate the syntax", so congratulations, it's you :)?? New > > syntaxes are risky; people are used to "${foo}" in other languages, and > > this is new and different, and sometimes different is scary.? But, it > > wasn't picked out of a hat; a good deal of thought has gone into this, > > and so while it may seem weird now, give it a year and I suspect it will > > not seem so odd.? (In any case, amber-dev isn't the forum for syntax > > discussions, so let's leave this here.) > > Just to clear the misunderstanding: I was not commenting on the "\{foo}", > I consider that a good thing (better than "${foo}" for sure, and "${foo}" > would not even be a compatible change). I was talking about the: > `STR."bla ${myVar} bla"`. As opposed to just calling the respective > methods. -------------- next part -------------- An HTML attachment was scrubbed... URL: From hjohn at xs4all.nl Thu Sep 22 13:37:39 2022 From: hjohn at xs4all.nl (John Hendrikx) Date: Thu, 22 Sep 2022 13:37:39 +0000 Subject: Deconstructors a bit unintuitive? Message-ID: I was reading https://github.com/openjdk/amber-docs/blob/master/eg-drafts/deconstruction-patterns-records-and-classes.md and wished to give some feedback. I find the (possible) approach presented for deconstructors a bit counter intuitive, where parameters are treated as outputs for deconstructors. I think that an approach where it more closely matches the standard pattern of " " would be more intuitive. The current approach instead looks very much like parameters that are passed by reference (pointers to those parameters) but without any indication (like "&" or "ref"), aside from the deconstructor keyword, that this is the case: public deconstructor Point(int x, int y) { x = this.x; y= this.y; } I find this quite jarring; overwriting (what looks like) a parameter with a new value never affected anything beyond the scope of the function. I realize that changing objects has effects beyond the function scope, but this was never the case for the parameter variables themselves. Deconstructors in my opinion seem to be an almost natural fit for another, more general, language feature: multiple return values. A deconstructor could then be nothing more than a normal method, or a static method with a special name (like constructors). Just to clarify my point if the above didn't make sense, I think deconstruction could be a lot closer to current Java like this: // static construction and deconstruction Point p = new Point(x, y); x, y = Point(p); // static construction and deconstruction with named method Point p = Point.of(x, y); x, y = Point.from(p); // instance method Point p = p.translate(10, 20); x, y = p.deconstruct(); // potentially taking parameters to affect result This would seem to be much closer to the inverse of construction. The declaration of the unnamed deconstructor would be the reverse of a constructor: class Point { Point(int x, int y) { ... } (int, int) Point { return this.x, this.y; } } I suppose this has been considered and discussed before, and introducing something as generic as multiple return values in order to support deconstructors in this way may be a bridge too far, but perhaps my feelings on this topic may still contribute something to this feature. --John -------------- next part -------------- An HTML attachment was scrubbed... URL: From nathan.h.walker at gmail.com Thu Sep 22 14:08:40 2022 From: nathan.h.walker at gmail.com (Nathan Walker) Date: Thu, 22 Sep 2022 10:08:40 -0400 Subject: String Templates Question - are expressions within a TemplateString literal evaluated immediately, or when a template is applied? Message-ID: Hi folks, Question regarding TemplateStrings that I could not seem to find the answer to in the JEP write up or the Javadoc for TemplateString: Is the expression evaluation lazy, or immediate? In other words, if I do something like this: List list=new ArrayList<>(); TemplateString ts = "size = \{list.size()}" list.add(1); System.out.println(ts.apply(STR)); Will it print out size = 0, or size = 1? My main reason for asking is that I would love for string templates to result in logging interfaces with overloaded methods that can take TemplateStrings instead of strings, and which are only evaluated if the log level is enabled. So that instead of something like this: if (logger.isEnabled(Level.DEBUG)) { logger.log(Level.DEBUG, "Expensive value is " + lookup() ); } we can just do: log.log(Level.DEBUG, "Expensive value is \{lookup()}"); And be confident that the cost of invoking lookup() will only be paid if DEBUG is enabled. Probably over 80% of the string building I have seen over my career has been related to building log messages. And the mistakes I have most often seen are: 1. Expensive message construction not guarded by checking if logging is enabled at that level 2. Expensive message construction guarded, but by checking the *wrong* logging level (e.g. check INFO, log at ERROR) 3. Using some sort of parameterized message/string format but getting the parameters wrong (out of order, missing one, having too many, etc.) If TemplateStrings evaluate their expression fragments only when processed then every one of these problems gets addressed, and logging code becomes *a lot* simpler to read, write, and code review. (Random side note: I *love* the choice of \ instead of $. It took some time for me to get use to the idea, but keeping the same escape character is fantastic and I wish more languages had done that instead of using $) Thanks for your time, Nathan -- Nathan H. Walker nathan.h.walker at gmail.com (703) 987-8937 -------------- next part -------------- An HTML attachment was scrubbed... URL: From james.laskey at oracle.com Thu Sep 22 14:17:40 2022 From: james.laskey at oracle.com (Jim Laskey) Date: Thu, 22 Sep 2022 14:17:40 +0000 Subject: String Templates Question - are expressions within a TemplateString literal evaluated immediately, or when a template is applied? In-Reply-To: References: Message-ID: <1E8047B0-729B-4CC3-9DC6-6A89DBE9785F@oracle.com> On Sep 22, 2022, at 11:08 AM, Nathan Walker > wrote: Hi folks, Question regarding TemplateStrings that I could not seem to find the answer to in the JEP write up or the Javadoc for TemplateString: Is the expression evaluation lazy, or immediate? In other words, if I do something like this: List list=new ArrayList<>(); TemplateString ts = "size = \{list.size()}" list.add(1); System.out.println(ts.apply(STR)); Will it print out size = 0, or size = 1? The expression is evaluated and the value is captured when creating the TemplatedString instance. There is some discussion around left to right evaluation that tries to clarify this. So the answer is ?size = 0?. My main reason for asking is that I would love for string templates to result in logging interfaces with overloaded methods that can take TemplateStrings instead of strings, and which are only evaluated if the log level is enabled. So that instead of something like this: if (logger.isEnabled(Level.DEBUG)) { logger.log(Level.DEBUG, "Expensive value is " + lookup() ); } we can just do: log.log(Level.DEBUG, "Expensive value is \{lookup()}"); And be confident that the cost of invoking lookup() will only be paid if DEBUG is enabled. Probably over 80% of the string building I have seen over my career has been related to building log messages. And the mistakes I have most often seen are: 1. Expensive message construction not guarded by checking if logging is enabled at that level 2. Expensive message construction guarded, but by checking the *wrong* logging level (e.g. check INFO, log at ERROR) 3. Using some sort of parameterized message/string format but getting the parameters wrong (out of order, missing one, having too many, etc.) If TemplateStrings evaluate their expression fragments only when processed then every one of these problems gets addressed, and logging code becomes *a lot* simpler to read, write, and code review. (Random side note: I *love* the choice of \ instead of $. It took some time for me to get use to the idea, but keeping the same escape character is fantastic and I wish more languages had done that instead of using $) You can play some tricks with Suppliers or Futures + lambdas to get lazy evaluation. I have an roughed out (i.e., old) example at https://cr.openjdk.java.net/~jlaskey/templates/examples/Log.java Cheers, ? Jim Thanks for your time, Nathan -- Nathan H. Walker nathan.h.walker at gmail.com (703) 987-8937 -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Sep 22 14:28:25 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 22 Sep 2022 10:28:25 -0400 Subject: Deconstructors a bit unintuitive? In-Reply-To: References: Message-ID: <023b59dd-bd7c-e8a5-5f19-4c7e81e0e76f@oracle.com> On 9/22/2022 9:37 AM, John Hendrikx wrote: > I was reading > https://github.com/openjdk/amber-docs/blob/master/eg-drafts/deconstruction-patterns-records-and-classes.md?and > wished to give some feedback. I appreciate the feedback, but I'm going to ask you hold it for a few reasons: ?- This feature isn't really even really under discussion yet - the document shown was merely an illustrative sketch, and no substantive discussion has been had yet; ?- Your concerns are mostly with the illustrative example syntax shown in that document, which should not be taken as a serious proposal.? (One of the trouble with such documents is that you have to show *something*, but then people will immediately assume "so this is the final syntax" and commence arguing.) ?- The syntax concerns of deconstructors are coupled to more complex forms of pattern declaration, whose considerations and requirements may not yet be obvious and are not considered in that document. Basically, that document was attempting to illustrate the dualities between constructors and deconstructors, and didn't want to try to do more, but of course it is hard to visualize these things without a code sketch, and hard to treat such code sketches for what they are. I'll try to speak a bit to your general conceptual issues, bearing in mind the constraints of the above. > I find the (possible) approach presented for deconstructors a bit > counter intuitive, where parameters are treated as outputs for > deconstructors. I think that an approach where it more closely matches > the standard pattern of " " would be more > intuitive. To some degree, declaring patterns is going to be uncomfortable somehow no matter what we do.? Its not obvious from the examples in the document, which were focused on a different aspect, but in generality patterns can have full argument lists of *both* input parameters and output bindings.? They simply will not fit cleanly in the "N in, one out" model of methods.? So there is going to be something counterintuitive somewhere, and the solution is likely to involve some degree of extending our mental model about executable members. > The current approach instead looks very much like parameters that are > passed by reference (pointers to those parameters) but without any > indication (like "&" or "ref"), aside from the deconstructor keyword, > that this is the case: > > ? ? ? public deconstructor Point(int x, int y) { x = this.x; y= this.y; } > > I find this quite jarring; overwriting (what looks like) a parameter > with a new value never affected anything beyond the scope of the > function. I realize that changing objects has effects beyond the > function scope, but this was never the case for the parameter > variables themselves. Your point is correct that the *position* of the binding list in the declaration is unnecessarily confusing, because it is where you would expect the parameters to be.? As it turns out, there are other reasons why this particular positioning has issues, but I'm not going to repeat the mistake by showing a more recent sketch, so we'll just have to wait for a "serious" proposal before continuing the discussion. > I suppose this has been considered and discussed before, and > introducing something as generic as multiple return values in order to > support deconstructors in this way may be a bridge too far, but > perhaps my feelings on this topic may still contribute something to > this feature. The "multiple return" interpretation is indeed one that springs easily to mind as a first thought, but it turns out to run out of gas before it gets where it needs to go.? (See "Isn't this just multiple return" in https://github.com/openjdk/amber-docs/blob/master/site/design-notes/patterns/pattern-match-object-model.md for one of the multiple reasons why I don't want to go in this direction.) The issues outlined in that section illustrate where this approach runs out of gas: it would only work with unconditional patterns. Deconstructors are unconditional, but it is a short hop to patterns that are the dual of, say, `Optional.of(v)`, and then treating destructuring as merely "invoking a multiple return method" falls apart because it has no way to express the conditionality.? And having a separate syntax for "invoking" conditional and unconditional patterns carries its own problems. Which brings us to my initial statement, which is that patterns are going to require extending our mental models about executable members.? It is probably better to discuss what that model is first. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Sep 22 17:20:55 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 22 Sep 2022 19:20:55 +0200 (CEST) Subject: Deconstructors a bit unintuitive? In-Reply-To: <023b59dd-bd7c-e8a5-5f19-4c7e81e0e76f@oracle.com> References: <023b59dd-bd7c-e8a5-5f19-4c7e81e0e76f@oracle.com> Message-ID: <707723336.10900611.1663867255673.JavaMail.zimbra@u-pem.fr> > From: "Brian Goetz" > To: "John Hendrikx" , "amber-dev" > Sent: Thursday, September 22, 2022 4:28:25 PM > Subject: Re: Deconstructors a bit unintuitive? > On 9/22/2022 9:37 AM, John Hendrikx wrote: >> I was reading [ >> https://github.com/openjdk/amber-docs/blob/master/eg-drafts/deconstruction-patterns-records-and-classes.md >> | >> https://github.com/openjdk/amber-docs/blob/master/eg-drafts/deconstruction-patterns-records-and-classes.md >> ] and wished to give some feedback. > I appreciate the feedback, but I'm going to ask you hold it for a few reasons: > - This feature isn't really even really under discussion yet - the document > shown was merely an illustrative sketch, and no substantive discussion has been > had yet; > - Your concerns are mostly with the illustrative example syntax shown in that > document, which should not be taken as a serious proposal. (One of the trouble > with such documents is that you have to show *something*, but then people will > immediately assume "so this is the final syntax" and commence arguing.) > - The syntax concerns of deconstructors are coupled to more complex forms of > pattern declaration, whose considerations and requirements may not yet be > obvious and are not considered in that document. > Basically, that document was attempting to illustrate the dualities between > constructors and deconstructors, and didn't want to try to do more, but of > course it is hard to visualize these things without a code sketch, and hard to > treat such code sketches for what they are. > I'll try to speak a bit to your general conceptual issues, bearing in mind the > constraints of the above. >> I find the (possible) approach presented for deconstructors a bit counter >> intuitive, where parameters are treated as outputs for deconstructors. I think >> that an approach where it more closely matches the standard pattern of >> " " would be more intuitive. > To some degree, declaring patterns is going to be uncomfortable somehow no > matter what we do. Its not obvious from the examples in the document, which > were focused on a different aspect, but in generality patterns can have full > argument lists of *both* input parameters and output bindings. They simply will > not fit cleanly in the "N in, one out" model of methods. So there is going to > be something counterintuitive somewhere, and the solution is likely to involve > some degree of extending our mental model about executable members. >> The current approach instead looks very much like parameters that are passed by >> reference (pointers to those parameters) but without any indication (like "&" >> or "ref"), aside from the deconstructor keyword, that this is the case: >> public deconstructor Point(int x, int y) { x = this.x; y= this.y; } >> I find this quite jarring; overwriting (what looks like) a parameter with a new >> value never affected anything beyond the scope of the function. I realize that >> changing objects has effects beyond the function scope, but this was never the >> case for the parameter variables themselves. > Your point is correct that the *position* of the binding list in the declaration > is unnecessarily confusing, because it is where you would expect the parameters > to be. As it turns out, there are other reasons why this particular positioning > has issues, but I'm not going to repeat the mistake by showing a more recent > sketch, so we'll just have to wait for a "serious" proposal before continuing > the discussion. >> I suppose this has been considered and discussed before, and introducing >> something as generic as multiple return values in order to support >> deconstructors in this way may be a bridge too far, but perhaps my feelings on >> this topic may still contribute something to this feature. > The "multiple return" interpretation is indeed one that springs easily to mind > as a first thought, but it turns out to run out of gas before it gets where it > needs to go. (See "Isn't this just multiple return" in [ > https://github.com/openjdk/amber-docs/blob/master/site/design-notes/patterns/pattern-match-object-model.md > | > https://github.com/openjdk/amber-docs/blob/master/site/design-notes/patterns/pattern-match-object-model.md > ] for one of the multiple reasons why I don't want to go in this direction.) > The issues outlined in that section illustrate where this approach runs out of > gas: it would only work with unconditional patterns. Deconstructors are > unconditional, but it is a short hop to patterns that are the dual of, say, > `Optional.of(v)`, and then treating destructuring as merely "invoking a > multiple return method" falls apart because it has no way to express the > conditionality. And having a separate syntax for "invoking" conditional and > unconditional patterns carries its own problems. With conditionality also comes exhaustivity, we need a way to declare that a group of pattern methods are exhaustive, e.g. the pattern methods Optional.of() and Optional.empty() are exhaustive. Because exhaustivity implies conditionnality, in term of syntax we may not need to express conditionality, but only two separate concerns, exhaustivity (or not) and return types. I think fostering exhaustivity when describing pattern methods play well with the idea of data oriented programming. > Which brings us to my initial statement, which is that patterns are going to > require extending our mental models about executable members. It is probably > better to discuss what that model is first. R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Sep 22 17:25:00 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 22 Sep 2022 13:25:00 -0400 Subject: Deconstructors a bit unintuitive? In-Reply-To: <707723336.10900611.1663867255673.JavaMail.zimbra@u-pem.fr> References: <023b59dd-bd7c-e8a5-5f19-4c7e81e0e76f@oracle.com> <707723336.10900611.1663867255673.JavaMail.zimbra@u-pem.fr> Message-ID: > With conditionality also comes exhaustivity, we need a way to declare > that a group of pattern methods are exhaustive, e.g. the pattern > methods Optional.of() and Optional.empty() are exhaustive. > Because exhaustivity implies conditionnality, in term of syntax we may > not need to express conditionality, but only two separate concerns, > exhaustivity (or not) and return types. > > I think fostering exhaustivity when describing pattern methods play > well with the idea of data oriented programming. Yes, this is on the requirements list. From steve at ethx.net Thu Sep 22 17:32:27 2022 From: steve at ethx.net (Steve Barham) Date: Thu, 22 Sep 2022 18:32:27 +0100 Subject: New candidate JEP: 430: String Templates (Preview) In-Reply-To: <20220920181019.B7D2D543703@eggemoggin.niobe.net> References: <20220920181019.B7D2D543703@eggemoggin.niobe.net> Message-ID: <1B90C6F7-EE89-4639-B99D-F598347D0EB6@ethx.net> > On 20 Sep 2022, at 19:10, Mark Reinhold wrote: > > https://openjdk.org/jeps/430 Really looking forward to this feature, and would echo the feedback about using \{ } to delimit expressions being a great way to disambiguate template strings from strings proper. One question I have is around the generality of the JLS change required to handle processors and templates. Application of a template processor feels an awful lot like application of a Function<>; the call effectively has type Function which for the specific types of TemplateProcessor and TemplatedString are blessed by the language, such that they support invocation via a dot operator. If in the future we have language support for application of Function<> interfaces (and associated overloads / parameterisations, ye even unto Function22), is the thinking that it would use a similar mechanism - i.e. STR.?Hello \{name}? is revealed to have been STR.apply(?Hello \{name}?) all along? If so, it feels like there might be an uneasy difference between 1-ary and n-ary parameter lists, in which we could have STR.?Hello \{name}?, but would need foo.(10, ?bar?) for application with multiple parameters. Handling application via brackets rather than dot (or via dot _and_ brackets) would seem to tidy things up a bit - we still have a special case for the moment in which TemplateProcessor can be invoked with a TemplatedString, it just looks like STR(?Hello \{name}?) or STR.(?Hello \{name}?), but has the benefit of leaving the application syntax open later for the more general case of n-ary application. Cheers, Steve From brian.goetz at oracle.com Thu Sep 22 18:21:35 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 22 Sep 2022 14:21:35 -0400 Subject: New candidate JEP: 430: String Templates (Preview) In-Reply-To: <1B90C6F7-EE89-4639-B99D-F598347D0EB6@ethx.net> References: <20220920181019.B7D2D543703@eggemoggin.niobe.net> <1B90C6F7-EE89-4639-B99D-F598347D0EB6@ethx.net> Message-ID: <97c18eb8-266b-dd64-f461-e3685977f064@oracle.com> It's a good intuition that there is a? connection here to function application, and it is not lost on us that some people really, really want to be able to take an instance of a functional interface and apply it without having to utter the method name.? Your proposed approach holds out the promise of killing two birds with one stone, but it comes laden with a strong assumption of where you want to go with function application.? (Full disclosure: I don't find the "pretend a functional interface is a function" feature very compelling, the benefit is mostly "code golf", and it has the negative effect of obscuring what f is.) But, let's explore the connection separate from that.? It would be a valid argument to say that TemplateProcessor is a SAM type, so we don't need a special syntax at all, we could just say ??? STR.apply("Hello \{name}") and this would say exactly what it means: construct a TemplatedString, and apply the STR processor to it.? What do you think people would say about this?? I don't think this would be popular, because people don't *want* to think about "construct a templated string, then apply a processor" -- they want to think of it as *interpret the templated string relative to the processor*.? (A lot of the time, they don't want to think about the processor at all, but that's a separate argument.)? The intermediate templated string is not an artifact that most users want to reason about, so we'd rather not shove it in their face. I think the same applies to a contracted form like `STR.("Hello \{name}")`; it encourages the user to think about the templated string as a separate value with its own type.? I don't think that improves the situation much; it may be five fewer characters, but it is still conceptually the same thing. There's a reason the language has a special syntactic form for string literals, rather than making strings by passing arrays of characters to a constructor, and there's a reason people have been clamoring for "string interpolation" for a long time, even though there exist templating libraries that can do the same thing.? We've chosen a point on the spectrum that offers more abstractive power than mere interpolation, but the syntax was chosen based on balancing how we think the users want to think about this, with ensuring that users have to make an explicit choice about how to process the template.? It is a fair question to ask whether a "special purpose" syntax like this carries its weight, but our bet is that Java developers would rather think of string processing as something special, even if it is "just applying a function." On 9/22/2022 1:32 PM, Steve Barham wrote: >> On 20 Sep 2022, at 19:10, Mark Reinhold wrote: >> >> https://openjdk.org/jeps/430 > Really looking forward to this feature, and would echo the feedback about using \{ } to delimit expressions being a great way to disambiguate template strings from strings proper. > > One question I have is around the generality of the JLS change required to handle processors and templates. > > Application of a template processor feels an awful lot like application of a Function<>; the call effectively has type Function which for the specific types of TemplateProcessor and TemplatedString are blessed by the language, such that they support invocation via a dot operator. > > If in the future we have language support for application of Function<> interfaces (and associated overloads / parameterisations, ye even unto Function22), is the thinking that it would use a similar mechanism - i.e. STR.?Hello \{name}? is revealed to have been STR.apply(?Hello \{name}?) all along? > > If so, it feels like there might be an uneasy difference between 1-ary and n-ary parameter lists, in which we could have STR.?Hello \{name}?, but would need foo.(10, ?bar?) for application with multiple parameters. > > Handling application via brackets rather than dot (or via dot _and_ brackets) would seem to tidy things up a bit - we still have a special case for the moment in which TemplateProcessor can be invoked with a TemplatedString, it just looks like STR(?Hello \{name}?) or STR.(?Hello \{name}?), but has the benefit of leaving the application syntax open later for the more general case of n-ary application. > > Cheers, > > Steve > -------------- next part -------------- An HTML attachment was scrubbed... URL: From nathan.h.walker at gmail.com Thu Sep 22 19:09:40 2022 From: nathan.h.walker at gmail.com (Nathan Walker) Date: Thu, 22 Sep 2022 15:09:40 -0400 Subject: String Templates Question - are expressions within a TemplateString literal evaluated immediately, or when a template is applied? In-Reply-To: <1E8047B0-729B-4CC3-9DC6-6A89DBE9785F@oracle.com> References: <1E8047B0-729B-4CC3-9DC6-6A89DBE9785F@oracle.com> Message-ID: That was all about what I expected. And it is probably the option that will result in the fewest surprises for developers. The log example you linked was very interesting. Has permitting lambda/method-reference syntax to insert lazy expressions been discussed before? I would really love to be able to do something like "The time is \{Instant::now}" or "The time is \{()->Instant.now()}" I think there is a lot of potential in allowing TemplateProcessors that elect to skip processing a template for some precondition avoid the overhead of any expensive embedded expressions, and I think this would be especially true in processors that can fail by throwing a checked exception. Thanks, Nathan On Thu, Sep 22, 2022 at 10:17 AM Jim Laskey wrote: > > > On Sep 22, 2022, at 11:08 AM, Nathan Walker > wrote: > > Hi folks, > > Question regarding TemplateStrings that I could not seem to find the > answer to in the JEP write up or the Javadoc for TemplateString: Is the > expression evaluation lazy, or immediate? > > In other words, if I do something like this: > > List list=new ArrayList<>(); > TemplateString ts = "size = \{list.size()}" > list.add(1); > System.out.println(ts.apply(STR)); > > Will it print out size = 0, or size = 1? > > > The expression is evaluated and the value is captured when creating the TemplatedString > instance. There is some discussion around left to right evaluation > that tries to clarify this. So the answer is ?size = 0?. > > > > My main reason for asking is that I would love for string templates to > result in logging interfaces with overloaded methods that can take > TemplateStrings instead of strings, and which are only evaluated if the log > level is enabled. So that instead of something like this: > > if (logger.isEnabled(Level.DEBUG)) { > logger.log(Level.DEBUG, "Expensive value is " + lookup() ); > } > > we can just do: > > log.log(Level.DEBUG, "Expensive value is \{lookup()}"); > > And be confident that the cost of invoking lookup() will only be paid if > DEBUG is enabled. > > Probably over 80% of the string building I have seen over my career has > been related to building log messages. And the mistakes I have most often > seen are: > > 1. Expensive message construction not guarded by checking if logging > is enabled at that level > 2. Expensive message construction guarded, but by checking the > *wrong* logging level (e.g. check INFO, log at ERROR) > 3. Using some sort of parameterized message/string format but getting > the parameters wrong (out of order, missing one, having too many, etc.) > > If TemplateStrings evaluate their expression fragments only when processed > then every one of these problems gets addressed, and logging code becomes > *a lot* simpler to read, write, and code review. > > (Random side note: I *love* the choice of \ instead of $. It took some > time for me to get use to the idea, but keeping the same escape character > is fantastic and I wish more languages had done that instead of using $) > > > You can play some tricks with Suppliers or Futures + lambdas to get lazy > evaluation. I have an roughed out (i.e., old) example at > https://cr.openjdk.java.net/~jlaskey/templates/examples/Log.java > > Cheers, > > ? Jim > > > > > Thanks for your time, > Nathan > -- > Nathan H. Walker > nathan.h.walker at gmail.com > (703) 987-8937 > > > > -- Nathan H. Walker nathan.h.walker at gmail.com (703) 987-8937 -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Thu Sep 22 19:13:35 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 22 Sep 2022 15:13:35 -0400 Subject: String Templates Question - are expressions within a TemplateString literal evaluated immediately, or when a template is applied? In-Reply-To: References: <1E8047B0-729B-4CC3-9DC6-6A89DBE9785F@oracle.com> Message-ID: <98f4bc15-bcef-0532-cbc5-04a7af9d78b7@oracle.com> We went around on this a few times, and in the end concluded that it is easy to just wrap the whole thing with a lambda: ??? () -> STR."Connection from \{name} at \{time}" which is a Supplier.? Lots of logging frameworks already are prepared to deal with Supplier. On 9/22/2022 3:09 PM, Nathan Walker wrote: > That was all about what I expected.? And it is probably the option > that will result in the fewest surprises for developers.? The log > example you linked was very interesting. > > Has permitting lambda/method-reference syntax to insert lazy > expressions been discussed before?? I would really love to be able to > do something like "The time is \{Instant::now}" or "The time is > \{()->Instant.now()}" > > I think there is a lot of potential in allowing TemplateProcessors > that elect to skip processing a template for some precondition avoid > the overhead of any expensive embedded expressions, and I think this > would be especially true in processors that can fail by throwing a > checked exception. > > Thanks, > Nathan > > On Thu, Sep 22, 2022 at 10:17 AM Jim Laskey > wrote: > > > >> On Sep 22, 2022, at 11:08 AM, Nathan Walker >> wrote: >> >> Hi folks, >> >> Question regarding TemplateStrings that I could not seem to find >> the answer to in the JEP write up or the Javadoc for >> TemplateString:? Is the expression evaluation lazy, or immediate? >> >> In other words, if I do something like this: >> >> ? ?List list=new ArrayList<>(); >> ? ?TemplateString ts = "size = \{list.size()}" >> ? ?list.add(1); >> ? ?System.out.println(ts.apply(STR)); >> >> Will it print out size = 0, or size = 1? > > The expression is evaluated and the value is captured when > creating the TemplatedString instance. There is some discussion > around left to right evaluation that?tries to?clarify this. So?the > answer is??size = 0?. > > >> >> My main reason for asking is that I would love for string >> templates to result in logging interfaces with overloaded methods >> that can take TemplateStrings instead of strings, and which are >> only evaluated if the log level is enabled.? So that instead of >> something like this: >> >> ? ?if (logger.isEnabled(Level.DEBUG)) { >> ? ? ? ? logger.log(Level.DEBUG, "Expensive value is " + lookup() ); >> ? ?} >> >> we can just do: >> >> ? ?log.log(Level.DEBUG, "Expensive value is \{lookup()}"); >> >> And be confident that the cost of invoking lookup() will only be >> paid if DEBUG is enabled. >> >> Probably over 80% of the string building I have seen over my >> career has been related to building log messages.? And the >> mistakes I have most often seen are: >> >> ? ?1. Expensive message construction not guarded by checking if >> logging is enabled at that level >> ? ?2. Expensive message construction guarded, but by checking the >> *wrong* logging level (e.g. check INFO, log at ERROR) >> ? ?3. Using some sort of parameterized message/string format but >> getting the parameters wrong (out of order, missing one, having >> too many, etc.) >> >> If TemplateStrings evaluate their expression fragments only when >> processed then every one of these problems gets addressed, and >> logging code becomes *a lot* simpler to read, write, and code review. >> >> (Random side note: I *love* the choice of \ instead of $.? It >> took some time for me to get use to the idea, but keeping the >> same escape character is fantastic and I wish more languages had >> done that instead of using $) > > You can play some tricks with Suppliers or Futures + lambdas to > get lazy evaluation. I have an?roughed out (i.e., old) example at > https://cr.openjdk.java.net/~jlaskey/templates/examples/Log.java > > Cheers, > > ? Jim > > > >> >> Thanks for your time, >> Nathan >> -- >> Nathan H. Walker >> nathan.h.walker at gmail.com >> (703) 987-8937 >> >> > > -- > Nathan H. Walker > nathan.h.walker at gmail.com > (703) 987-8937 > > -------------- next part -------------- An HTML attachment was scrubbed... URL: From john.r.rose at oracle.com Thu Sep 22 20:37:37 2022 From: john.r.rose at oracle.com (John Rose) Date: Thu, 22 Sep 2022 13:37:37 -0700 Subject: String Templates Question - are expressions within a TemplateString literal evaluated immediately, or when a template is applied? In-Reply-To: <98f4bc15-bcef-0532-cbc5-04a7af9d78b7@oracle.com> References: <1E8047B0-729B-4CC3-9DC6-6A89DBE9785F@oracle.com> <98f4bc15-bcef-0532-cbc5-04a7af9d78b7@oracle.com> Message-ID: <8E7BFB17-2585-4275-8EC6-592C4BEA84A5@oracle.com> Logging is a good use case (but not at all the only one) for this move of ?quoting? an expression (for later ?unquoting?) with a nullary lambda prefix. I put a discussion about this topic here: ?Java supplier lambda, the Ultimate Quote? http://cr.openjdk.java.net/~jrose/jls/eta-abstraction-expr-quoting.html If we had an ?auto-quote? feature (see the appendix of the above doc) logger classes could get the delayed execution as part of their API declarations, in the way that Nathan is apparently envisioning. But in practice, adding `()->` before your logger expressions is not a huge burden. Yeah, you?d want to say `LOG."template \{stuff}"` instead of `log(()->STR.?template \{stuff}?)`. But it?s not so terrible as to motivate (all by itself) sugary elision of the `()->` quote. For my part, I?m really really glad that string templates did not add a hard-coded feature into their design for this sort of expression quoting. I think such a feature deserves to be considered on its own, separately for many kinds of APIs, if at all. (Idea of the moment: Allow the string template processor `apply` method to accept a `Supplier`, instead of `ST`. Then push an auto-quote rule into the desugaring of `LOG.<>`, when `LOG::apply` takes a supplier. This could be added compatibly to the present design, as a future feature. Sort of like some proposals to add new rules for enhanced-for over streams, by adjusting the types around the enhanced-for. String templates could support many, many such compatible additions, IMO, and they should be rolled out in a measured and cautious way, if at all.) On 22 Sep 2022, at 12:13, Brian Goetz wrote: > We went around on this a few times, and in the end concluded that it > is easy to just wrap the whole thing with a lambda: > > ??? () -> STR."Connection from \{name} at \{time}" > > which is a Supplier.? Lots of logging frameworks already are > prepared to deal with Supplier. > > On 9/22/2022 3:09 PM, Nathan Walker wrote: >> That was all about what I expected.? And it is probably the option >> that will result in the fewest surprises for developers.? The log >> example you linked was very interesting. >> >> Has permitting lambda/method-reference syntax to insert lazy >> expressions been discussed before?? I would really love to be able >> to do something like "The time is \{Instant::now}" or "The time is >> \{()->Instant.now()}" >> >> I think there is a lot of potential in allowing TemplateProcessors >> that elect to skip processing a template for some precondition avoid >> the overhead of any expensive embedded expressions, and I think this >> would be especially true in processors that can fail by throwing a >> checked exception. >> >> Thanks, >> Nathan >> >> On Thu, Sep 22, 2022 at 10:17 AM Jim Laskey >> wrote: >> >> >> >>> On Sep 22, 2022, at 11:08 AM, Nathan Walker >>> wrote: >>> >>> Hi folks, >>> >>> Question regarding TemplateStrings that I could not seem to find >>> the answer to in the JEP write up or the Javadoc for >>> TemplateString:? Is the expression evaluation lazy, or >>> immediate? >>> >>> In other words, if I do something like this: >>> >>> ? ?List list=new ArrayList<>(); >>> ? ?TemplateString ts = "size = \{list.size()}" >>> ? ?list.add(1); >>> ? ?System.out.println(ts.apply(STR)); >>> >>> Will it print out size = 0, or size = 1? >> >> The expression is evaluated and the value is captured when >> creating the TemplatedString instance. There is some discussion >> around left to right evaluation that?tries to?clarify this. >> So?the >> answer is??size = 0?. >> >> >>> >>> My main reason for asking is that I would love for string >>> templates to result in logging interfaces with overloaded >>> methods >>> that can take TemplateStrings instead of strings, and which are >>> only evaluated if the log level is enabled.? So that instead of >>> something like this: >>> >>> ? ?if (logger.isEnabled(Level.DEBUG)) { >>> ? ? ? ? logger.log(Level.DEBUG, "Expensive value is " + >>> lookup() ); >>> ? ?} >>> >>> we can just do: >>> >>> ? ?log.log(Level.DEBUG, "Expensive value is \{lookup()}"); >>> >>> And be confident that the cost of invoking lookup() will only be >>> paid if DEBUG is enabled. >>> >>> Probably over 80% of the string building I have seen over my >>> career has been related to building log messages.? And the >>> mistakes I have most often seen are: >>> >>> ? ?1. Expensive message construction not guarded by checking >>> if >>> logging is enabled at that level >>> ? ?2. Expensive message construction guarded, but by checking >>> the >>> *wrong* logging level (e.g. check INFO, log at ERROR) >>> ? ?3. Using some sort of parameterized message/string format >>> but >>> getting the parameters wrong (out of order, missing one, having >>> too many, etc.) >>> >>> If TemplateStrings evaluate their expression fragments only when >>> processed then every one of these problems gets addressed, and >>> logging code becomes *a lot* simpler to read, write, and code >>> review. >>> >>> (Random side note: I *love* the choice of \ instead of $.? It >>> took some time for me to get use to the idea, but keeping the >>> same escape character is fantastic and I wish more languages had >>> done that instead of using $) >> >> You can play some tricks with Suppliers or Futures + lambdas to >> get lazy evaluation. I have an?roughed out (i.e., old) example >> at >> https://cr.openjdk.java.net/~jlaskey/templates/examples/Log.java >> >> Cheers, >> >> ? Jim >> >> >> >>> >>> Thanks for your time, >>> Nathan >>> -- >>> Nathan H. Walker >>> nathan.h.walker at gmail.com >>> (703) 987-8937 >>> >>> >> >> -- >> Nathan H. Walker >> nathan.h.walker at gmail.com >> (703) 987-8937 >> >> -------------- next part -------------- An HTML attachment was scrubbed... URL: From alex.buckley at oracle.com Thu Sep 22 21:01:37 2022 From: alex.buckley at oracle.com (Alex Buckley) Date: Thu, 22 Sep 2022 14:01:37 -0700 Subject: String Templates Question - are expressions within a TemplateString literal evaluated immediately, or when a template is applied? In-Reply-To: <8E7BFB17-2585-4275-8EC6-592C4BEA84A5@oracle.com> References: <1E8047B0-729B-4CC3-9DC6-6A89DBE9785F@oracle.com> <98f4bc15-bcef-0532-cbc5-04a7af9d78b7@oracle.com> <8E7BFB17-2585-4275-8EC6-592C4BEA84A5@oracle.com> Message-ID: The JEP also notes that "... it is not possible for a template processor to obtain, from a TemplatedString, the exact characters which appear in a template in source code; only the values of the embedded expressions are available, not the embedded expressions themselves." If a future Java platform allows the embedded expressions themselves to be represented at run time (e.g. via a dynamically-computed constant that denotes the AST seen at compile time), then a template processor could choose to (re)evaluate an embedded expression in the current environment, no lambda "quoting" needed. A string template expression's deference to a template processor during evaluation is most unusual, and quite magical. Alex On 9/22/2022 1:37 PM, John Rose wrote: > Logging is a good use case (but not at all the only one) for this move > of ?quoting? an expression (for later ?unquoting?) with a nullary lambda > prefix. > > I put a discussion about this topic here: > ?Java supplier lambda, the Ultimate Quote? > http://cr.openjdk.java.net/~jrose/jls/eta-abstraction-expr-quoting.html > > > If we had an ?auto-quote? feature (see the appendix of the above doc) > logger classes could get the delayed execution as part of their API > declarations, in the way that Nathan is apparently envisioning. But in > practice, adding |()->| before your logger expressions is not a huge > burden. Yeah, you?d want to say |LOG."template \{stuff}"| instead of > |log(()->STR.?template \{stuff}?)|. But it?s not so terrible as to > motivate (all by itself) sugary elision of the |()->| quote. > > For my part, I?m really really glad that string templates did not add a > hard-coded feature into their design for this sort of expression > quoting. I think such a feature deserves to be considered on its own, > separately for many kinds of APIs, if at all. > > (Idea of the moment: Allow the string template processor |apply| method > to accept a |Supplier|, instead of |ST|. Then push an auto-quote > rule into the desugaring of |LOG.<>|, when |LOG::apply| takes a > supplier. This could be added compatibly to the present design, as a > future feature. Sort of like some proposals to add new rules for > enhanced-for over streams, by adjusting the types around the > enhanced-for. String templates could support many, many such compatible > additions, IMO, and they should be rolled out in a measured and cautious > way, if at all.) > > On 22 Sep 2022, at 12:13, Brian Goetz wrote: > > We went around on this a few times, and in the end concluded that it > is easy to just wrap the whole thing with a lambda: > > ??? () -> STR."Connection from \{name} at \{time}" > > which is a Supplier.? Lots of logging frameworks already are > prepared to deal with Supplier. > > On 9/22/2022 3:09 PM, Nathan Walker wrote: >> That was all about what I expected.? And it is probably the option >> that will result in the fewest surprises for developers.? The log >> example you linked was very interesting. >> >> Has permitting lambda/method-reference syntax to insert lazy >> expressions been discussed before?? I would really love to be able >> to do something like "The time is \{Instant::now}" or "The time is >> \{()->Instant.now()}" >> >> I think there is a lot of potential in allowing TemplateProcessors >> that elect to skip processing a template for some precondition >> avoid the overhead of any expensive embedded expressions, and I >> think this would be especially true in processors that can fail by >> throwing a checked exception. >> >> Thanks, >> Nathan >> >> On Thu, Sep 22, 2022 at 10:17 AM Jim Laskey >> wrote: >> >> >> >>> On Sep 22, 2022, at 11:08 AM, Nathan Walker >>> wrote: >>> >>> Hi folks, >>> >>> Question regarding TemplateStrings that I could not seem to >>> find the answer to in the JEP write up or the Javadoc for >>> TemplateString:? Is the expression evaluation lazy, or immediate? >>> >>> In other words, if I do something like this: >>> >>> ? ? ?List list=new ArrayList<>(); >>> ? ? ?TemplateString ts = "size = \{list.size()}" >>> ? ? ?list.add(1); >>> ? ? ?System.out.println(ts.apply(STR)); >>> >>> Will it print out size = 0, or size = 1? >> >> The expression is evaluated and the value is captured when >> creating the TemplatedString instance. There is some >> discussion around left to right evaluation that?tries >> to?clarify this. So?the answer is??size = 0?. >> >> >>> >>> My main reason for asking is that I would love for string >>> templates to result in logging interfaces with overloaded >>> methods that can take TemplateStrings instead of strings, and >>> which are only evaluated if the log level is enabled.? So >>> that instead of something like this: >>> >>> ? ? ?if (logger.isEnabled(Level.DEBUG)) { >>> ? ? ? ? ? logger.log(Level.DEBUG, "Expensive value is " + >>> lookup() ); >>> ? ? ?} >>> >>> we can just do: >>> >>> ? ? ?log.log(Level.DEBUG, "Expensive value is \{lookup()}"); >>> >>> And be confident that the cost of invoking lookup() will only >>> be paid if DEBUG is enabled. >>> >>> Probably over 80% of the string building I have seen over my >>> career has been related to building log messages.? And the >>> mistakes I have most often seen are: >>> >>> ? ? ?1. Expensive message construction not guarded by >>> checking if logging is enabled at that level >>> ? ? ?2. Expensive message construction guarded, but by >>> checking the *wrong* logging level (e.g. check INFO, log at >>> ERROR) >>> ? ? ?3. Using some sort of parameterized message/string >>> format but getting the parameters wrong (out of order, >>> missing one, having too many, etc.) >>> >>> If TemplateStrings evaluate their expression fragments only >>> when processed then every one of these problems gets >>> addressed, and logging code becomes *a lot* simpler to read, >>> write, and code review. >>> >>> (Random side note: I *love* the choice of \ instead of $.? It >>> took some time for me to get use to the idea, but keeping the >>> same escape character is fantastic and I wish more languages >>> had done that instead of using $) >> >> You can play some tricks with Suppliers or Futures + lambdas >> to get lazy evaluation. I have an?roughed out (i.e., old) >> example at >> https://cr.openjdk.java.net/~jlaskey/templates/examples/Log.java >> >> Cheers, >> >> ? Jim >> >> >> >>> >>> Thanks for your time, >>> Nathan >>> -- >>> Nathan H. Walker >>> nathan.h.walker at gmail.com >>> (703) 987-8937 >>> >>> >> >> -- >> Nathan H. Walker >> nathan.h.walker at gmail.com >> (703) 987-8937 >> >> > From john.r.rose at oracle.com Thu Sep 22 21:23:57 2022 From: john.r.rose at oracle.com (John Rose) Date: Thu, 22 Sep 2022 14:23:57 -0700 Subject: String Templates Question - are expressions within a TemplateString literal evaluated immediately, or when a template is applied? In-Reply-To: References: <1E8047B0-729B-4CC3-9DC6-6A89DBE9785F@oracle.com> <98f4bc15-bcef-0532-cbc5-04a7af9d78b7@oracle.com> <8E7BFB17-2585-4275-8EC6-592C4BEA84A5@oracle.com> Message-ID: <96246C6A-307D-4AA6-A7F7-8CB09DECD221@oracle.com> On 22 Sep 2022, at 14:01, Alex Buckley wrote: > If a future Java platform allows the embedded expressions themselves > to be represented at run time (e.g. via a dynamically-computed > constant that denotes the AST seen at compile time), Yes, that?s the essence of what I mean by ?expression quoting?. (A complicating feature is that expressions can refer to local variables. The quoting mechanism must define whether and how these variables are ?captured? in the AST-like constant. The comparison here with variable capture by lambdas is inescapable.) > then a template processor could choose to (re)evaluate an embedded > expression in the current environment, no lambda "quoting" needed. Yes. There are many possible ways to signal that an expression has to be stored in some ?transparently reprocessable? form, in the class file, and there are any number of ways to store such expressions in a classfile. (The basic bytecode sequences used today don?t cut it, since they are not self-delimiting, and cannot be recontextualized or destructured like an AST could be.) But: Almost any expression quoting facility I can imagine for Java is largely isomorphic with a lambda-based ?quoting? feature, disregarding details of surface syntax. (The bytecode translation of a ?crackable? lambda would include a bundle of AST associated with the lambda object.) Surface syntax itself is fungible: quote-eliding sugar can make quotes (such as the `()->` ?operator?) disappear altogether from the source code, if we choose. Auto-quoting could be gated on logic similar to auto-boxing. Or not. (And whether or not lambdas are part of the story.) One disconnect from lambdas (per se) is that expressions like `a[i++]` can produce multiple results via side effects on local variables, which lambdas cannot manage directly. So quoting `a[i++]` is not just a matter of saying `()->a[i++]` with or without lambda cracking. But perhaps `a[i++]` could be modeled using pattern-based APIs which output multiple named bindings. And if pattern-deconstructor lambdas ?were a thing? then once again `()->a[i++]` could be a candidate syntax, as well as its auto-quoted version `a[i++]` (contextually processed). To be clear: I?m not proposing anything here, just commenting on the larger design landscape as I see it. I?ve probably said more than enough for today?s purposes, but I do know people are thinking and dreaming about such things, based on discussions of lambda cracking at JVM Language Summits of old. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Thu Sep 22 21:25:42 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 22 Sep 2022 23:25:42 +0200 (CEST) Subject: String Templates Question - are expressions within a TemplateString literal evaluated immediately, or when a template is applied? In-Reply-To: References: <1E8047B0-729B-4CC3-9DC6-6A89DBE9785F@oracle.com> <98f4bc15-bcef-0532-cbc5-04a7af9d78b7@oracle.com> <8E7BFB17-2585-4275-8EC6-592C4BEA84A5@oracle.com> Message-ID: <145902090.10980628.1663881942169.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Alex Buckley" > To: "John Rose" , "Brian Goetz" > Cc: "Nathan Walker" , "Jim Laskey" , "amber-dev" > > Sent: Thursday, September 22, 2022 11:01:37 PM > Subject: Re: String Templates Question - are expressions within a TemplateString literal evaluated immediately, or when > a template is applied? > The JEP also notes that "... it is not possible for a template processor > to obtain, from a TemplatedString, the exact characters which appear in > a template in source code; only the values of the embedded expressions > are available, not the embedded expressions themselves." > > If a future Java platform allows the embedded expressions themselves to > be represented at run time (e.g. via a dynamically-computed constant > that denotes the AST seen at compile time), then a template processor > could choose to (re)evaluate an embedded expression in the current > environment, no lambda "quoting" needed. A string template expression's > deference to a template processor during evaluation is most unusual, and > quite magical. Usually you still need quoting because you do not know where to draw the line in between where things are values and where things are ASTs. Otherwise the whole function, the whole class, the whole library/application becomes an AST. > > Alex R2mi > > On 9/22/2022 1:37 PM, John Rose wrote: >> Logging is a good use case (but not at all the only one) for this move >> of ?quoting? an expression (for later ?unquoting?) with a nullary lambda >> prefix. >> >> I put a discussion about this topic here: >> ?Java supplier lambda, the Ultimate Quote? >> http://cr.openjdk.java.net/~jrose/jls/eta-abstraction-expr-quoting.html >> >> >> If we had an ?auto-quote? feature (see the appendix of the above doc) >> logger classes could get the delayed execution as part of their API >> declarations, in the way that Nathan is apparently envisioning. But in >> practice, adding |()->| before your logger expressions is not a huge >> burden. Yeah, you?d want to say |LOG."template \{stuff}"| instead of >> |log(()->STR.?template \{stuff}?)|. But it?s not so terrible as to >> motivate (all by itself) sugary elision of the |()->| quote. >> >> For my part, I?m really really glad that string templates did not add a >> hard-coded feature into their design for this sort of expression >> quoting. I think such a feature deserves to be considered on its own, >> separately for many kinds of APIs, if at all. >> >> (Idea of the moment: Allow the string template processor |apply| method >> to accept a |Supplier|, instead of |ST|. Then push an auto-quote >> rule into the desugaring of |LOG.<>|, when |LOG::apply| takes a >> supplier. This could be added compatibly to the present design, as a >> future feature. Sort of like some proposals to add new rules for >> enhanced-for over streams, by adjusting the types around the >> enhanced-for. String templates could support many, many such compatible >> additions, IMO, and they should be rolled out in a measured and cautious >> way, if at all.) >> >> On 22 Sep 2022, at 12:13, Brian Goetz wrote: >> >> We went around on this a few times, and in the end concluded that it >> is easy to just wrap the whole thing with a lambda: >> >> ??? () -> STR."Connection from \{name} at \{time}" >> >> which is a Supplier.? Lots of logging frameworks already are >> prepared to deal with Supplier. >> >> On 9/22/2022 3:09 PM, Nathan Walker wrote: >>> That was all about what I expected.? And it is probably the option >>> that will result in the fewest surprises for developers.? The log >>> example you linked was very interesting. >>> >>> Has permitting lambda/method-reference syntax to insert lazy >>> expressions been discussed before?? I would really love to be able >>> to do something like "The time is \{Instant::now}" or "The time is >>> \{()->Instant.now()}" >>> >>> I think there is a lot of potential in allowing TemplateProcessors >>> that elect to skip processing a template for some precondition >>> avoid the overhead of any expensive embedded expressions, and I >>> think this would be especially true in processors that can fail by >>> throwing a checked exception. >>> >>> Thanks, >>> Nathan >>> >>> On Thu, Sep 22, 2022 at 10:17 AM Jim Laskey >>> wrote: >>> >>> >>> >>>> On Sep 22, 2022, at 11:08 AM, Nathan Walker >>>> wrote: >>>> >>>> Hi folks, >>>> >>>> Question regarding TemplateStrings that I could not seem to >>>> find the answer to in the JEP write up or the Javadoc for >>>> TemplateString:? Is the expression evaluation lazy, or immediate? >>>> >>>> In other words, if I do something like this: >>>> >>>> ? ? ?List list=new ArrayList<>(); >>>> ? ? ?TemplateString ts = "size = \{list.size()}" >>>> ? ? ?list.add(1); >>>> ? ? ?System.out.println(ts.apply(STR)); >>>> >>>> Will it print out size = 0, or size = 1? >>> >>> The expression is evaluated and the value is captured when >>> creating the TemplatedString instance. There is some >>> discussion around left to right evaluation that?tries >>> to?clarify this. So?the answer is??size = 0?. >>> >>> >>>> >>>> My main reason for asking is that I would love for string >>>> templates to result in logging interfaces with overloaded >>>> methods that can take TemplateStrings instead of strings, and >>>> which are only evaluated if the log level is enabled.? So >>>> that instead of something like this: >>>> >>>> ? ? ?if (logger.isEnabled(Level.DEBUG)) { >>>> ? ? ? ? ? logger.log(Level.DEBUG, "Expensive value is " + >>>> lookup() ); >>>> ? ? ?} >>>> >>>> we can just do: >>>> >>>> ? ? ?log.log(Level.DEBUG, "Expensive value is \{lookup()}"); >>>> >>>> And be confident that the cost of invoking lookup() will only >>>> be paid if DEBUG is enabled. >>>> >>>> Probably over 80% of the string building I have seen over my >>>> career has been related to building log messages.? And the >>>> mistakes I have most often seen are: >>>> >>>> ? ? ?1. Expensive message construction not guarded by >>>> checking if logging is enabled at that level >>>> ? ? ?2. Expensive message construction guarded, but by >>>> checking the *wrong* logging level (e.g. check INFO, log at >>>> ERROR) >>>> ? ? ?3. Using some sort of parameterized message/string >>>> format but getting the parameters wrong (out of order, >>>> missing one, having too many, etc.) >>>> >>>> If TemplateStrings evaluate their expression fragments only >>>> when processed then every one of these problems gets >>>> addressed, and logging code becomes *a lot* simpler to read, >>>> write, and code review. >>>> >>>> (Random side note: I *love* the choice of \ instead of $.? It >>>> took some time for me to get use to the idea, but keeping the >>>> same escape character is fantastic and I wish more languages >>>> had done that instead of using $) >>> >>> You can play some tricks with Suppliers or Futures + lambdas >>> to get lazy evaluation. I have an?roughed out (i.e., old) >>> example at >>> https://cr.openjdk.java.net/~jlaskey/templates/examples/Log.java >>> >>> Cheers, >>> >>> ? Jim >>> >>> >>> >>>> >>>> Thanks for your time, >>>> Nathan >>>> -- >>>> Nathan H. Walker >>>> nathan.h.walker at gmail.com >>>> (703) 987-8937 >>>> >>>> >>> >>> -- >>> Nathan H. Walker >>> nathan.h.walker at gmail.com >>> (703) 987-8937 >>> >>> From attila.kelemen85 at gmail.com Wed Sep 21 23:17:49 2022 From: attila.kelemen85 at gmail.com (Attila Kelemen) Date: Thu, 22 Sep 2022 01:17:49 +0200 Subject: New candidate JEP: 430: String Templates (Preview) In-Reply-To: <3e8ed816-aa51-85a3-cd71-472b8e5a0bc6@oracle.com> References: <20220920181019.B7D2D543703@eggemoggin.niobe.net> <3e8ed816-aa51-85a3-cd71-472b8e5a0bc6@oracle.com> Message-ID: Thanks for the responses. See some of my clarifications below. > > > > 1. Might be a personal preference, but I find the > > `TemplateProcessorExpression . Template` syntax bizarre. > > We knew that at least one out of the 10M Java developers would loudly > proclaim "I hate the syntax", so congratulations, it's you :) New > syntaxes are risky; people are used to "${foo}" in other languages, and > this is new and different, and sometimes different is scary. But, it > wasn't picked out of a hat; a good deal of thought has gone into this, > and so while it may seem weird now, give it a year and I suspect it will > not seem so odd. (In any case, amber-dev isn't the forum for syntax > discussions, so let's leave this here.) Just to clear the misunderstanding: I was not commenting on the "\{foo}", I consider that a good thing (better than "${foo}" for sure, and "${foo}" would not even be a compatible change). I was talking about the: `STR."bla ${myVar} bla"`. As opposed to just calling the respective methods. > > > 2. The `TemplatedString` is more complicated than necessary. It is not > > obvious by just looking at it, how to > > implement a `TemplateProcessor`. > > Perhaps, but the ratio of people who *use* TemplateProcessor to those > who *write* it is probably 1,000 to one or more. And there are a lot of > hidden requirements that have been sussed out as a result of writing not > only the obvious processors, but some less common ones as well. So what > seems unnecessary to you may simply be that you are thinking of simpler > use cases. Technically, both can do the same (except that the current way sacrifices a single character), and you might even provide such iterators, if you want to with the effective `List` way. Also true the other way around, you could provide a `List getAllParts()` (or maybe better with `Iterator`). I know it might sound so, but I'm not trying to be nit picky. I'm honestly curious what is the benefit of the current way? Less memory usage? > > > 4. Last time I saw, the compiler generated a new class for each > > `TemplatedString` instance > > You are aware there is a class generated per lambda / mref too, right? > And yet no one is saying "Lambda translation is too heavy." (There are > also options available to improve the translation, but it's still early > for that game.) Sure, but there it is obviously needed. It was just very surprising to me, that this was deemed more efficient. Though, I'm not too concerned about this one, since this is not covered by the JEP, and can be changed even after introducing it, and I'm sure eventually the more efficient way will win. I was just surprised. From ebresie at gmail.com Wed Sep 21 23:45:05 2022 From: ebresie at gmail.com (Eric Bresie) Date: Wed, 21 Sep 2022 23:45:05 +0000 Subject: New candidate JEP: 430: String Templates (Preview) In-Reply-To: References: <20220920181019.B7D2D543703@eggemoggin.niobe.net> <3e8ed816-aa51-85a3-cd71-472b8e5a0bc6@oracle.com> Message-ID: Just curious, doesn?t JSP/JSF processing already use a ?string template? type paradigm using a ${} type syntax. Would any changes here be usable either or or there? Given recent log4j security issues, is there any possible risk that expansion could introduce some exploitable logic? Does any sort of constraint or mechanism need to protect against that or am I I er thinking it? Get Outlook for iOS ________________________________ From: jdk-dev on behalf of Attila Kelemen Sent: Wednesday, September 21, 2022 6:20 PM To: Brian Goetz Cc: amber-dev at openjdk.org ; jdk-dev at openjdk.org Subject: Re: New candidate JEP: 430: String Templates (Preview) Thanks for the responses. See some of my clarifications below. > > > > 1. Might be a personal preference, but I find the > > `TemplateProcessorExpression . Template` syntax bizarre. > > We knew that at least one out of the 10M Java developers would loudly > proclaim "I hate the syntax", so congratulations, it's you :) New > syntaxes are risky; people are used to "${foo}" in other languages, and > this is new and different, and sometimes different is scary. But, it > wasn't picked out of a hat; a good deal of thought has gone into this, > and so while it may seem weird now, give it a year and I suspect it will > not seem so odd. (In any case, amber-dev isn't the forum for syntax > discussions, so let's leave this here.) Just to clear the misunderstanding: I was not commenting on the "\{foo}", I consider that a good thing (better than "${foo}" for sure, and "${foo}" would not even be a compatible change). I was talking about the: `STR."bla ${myVar} bla"`. As opposed to just calling the respective methods. -------------- next part -------------- An HTML attachment was scrubbed... URL: From michel.turpin1 at gmail.com Thu Sep 22 22:30:32 2022 From: michel.turpin1 at gmail.com (Michel Turpin) Date: Fri, 23 Sep 2022 00:30:32 +0200 Subject: Bad exhaustive test using Record Pattern as delivered in OpenJDK 19 preview Message-ID: Hello, First, please excuse me for the way I send this bug report. I cannot find a way to report it in other ways. Looking at the description of JEP 405, it made me think of Rust enums and pattern matching. I tried then to play on how to write a copy of the Rust "Option" enum : https://gist.github.com/grimly/55d414f0cc3395e87a7c813176c50ac9 But then I encountered this issue when narrowing the types I use and in this particular instance ( https://gist.github.com/grimly/55d414f0cc3395e87a7c813176c50ac9#file-testfailing-java-L9-L12 ), the compiler throws an error and reports the switch is not exhaustive. Given in my code "Option" is sealed and may only be "Some" or "None", I would expect listing both options to make my cases exhaustive. Adding a default case, I also fail to produce an instance that would trigger it. Best regards, Michel -- Michel TURPIN -------------- next part -------------- An HTML attachment was scrubbed... URL: From tjmnkrajyej at gmail.com Fri Sep 23 15:54:58 2022 From: tjmnkrajyej at gmail.com (tjmnkrajyej at gmail.com) Date: Fri, 23 Sep 2022 16:54:58 +0100 Subject: Bad exhaustive test using Record Pattern as delivered in OpenJDK 19 preview In-Reply-To: References: Message-ID: `opt` can be `null`. > Hello, > > First, please excuse me for the way I send this bug report. I cannot > find a way to report it in other ways. > > Looking at the description of JEP 405, it made me think of Rust enums > and pattern matching. > I tried then to play on how to write a copy of the Rust "Option" enum > : https://gist.github.com/grimly/55d414f0cc3395e87a7c813176c50ac9 > > But then I encountered this issue when narrowing the types I use and > in this particular instance ( > https://gist.github.com/grimly/55d414f0cc3395e87a7c813176c50ac9#file-testfailing-java-L9-L12 > ), the compiler throws an error and reports the switch is not exhaustive. > > Given in my code "Option" is sealed and may only be "Some" or "None", > I would expect listing both options to make my cases exhaustive. > Adding a default case, I also fail to produce an instance that would > trigger it. > > Best regards, > Michel > > -- > > Michel TURPIN > -------------- next part -------------- An HTML attachment was scrubbed... URL: From tjmnkrajyej at gmail.com Fri Sep 23 16:03:08 2022 From: tjmnkrajyej at gmail.com (tjmnkrajyej at gmail.com) Date: Fri, 23 Sep 2022 17:03:08 +0100 Subject: Bad exhaustive test using Record Pattern as delivered in OpenJDK 19 preview In-Reply-To: References: Message-ID: `opt` can be `null`. > Hello, > > First, please excuse me for the way I send this bug report. I cannot > find a way to report it in other ways. > > Looking at the description of JEP 405, it made me think of Rust enums > and pattern matching. > I tried then to play on how to write a copy of the Rust "Option" enum > : https://gist.github.com/grimly/55d414f0cc3395e87a7c813176c50ac9 > > But then I encountered this issue when narrowing the types I use and > in this particular instance ( > https://gist.github.com/grimly/55d414f0cc3395e87a7c813176c50ac9#file-testfailing-java-L9-L12 > ), the compiler throws an error and reports the switch is not exhaustive. > > Given in my code "Option" is sealed and may only be "Some" or "None", > I would expect listing both options to make my cases exhaustive. > Adding a default case, I also fail to produce an instance that would > trigger it. > > Best regards, > Michel > > -- > > Michel TURPIN > -------------- next part -------------- An HTML attachment was scrubbed... URL: From michel.turpin1 at gmail.com Fri Sep 23 18:33:53 2022 From: michel.turpin1 at gmail.com (Michel TURPIN) Date: Fri, 23 Sep 2022 20:33:53 +0200 Subject: Bad exhaustive test using Record Pattern as delivered in OpenJDK 19 preview In-Reply-To: References: , Message-ID: <38C1A33E-9DA5-4794-A807-E6D9371AA973@hxcore.ol> An HTML attachment was scrubbed... URL: From michel.turpin1 at gmail.com Fri Sep 23 18:33:53 2022 From: michel.turpin1 at gmail.com (Michel TURPIN) Date: Fri, 23 Sep 2022 20:33:53 +0200 Subject: Bad exhaustive test using Record Pattern as delivered in OpenJDK 19 preview In-Reply-To: References: , Message-ID: <38C1A33E-9DA5-4794-A807-E6D9371AA973@hxcore.ol> An HTML attachment was scrubbed... URL: From ice1000kotlin at foxmail.com Sat Sep 24 05:29:46 2022 From: ice1000kotlin at foxmail.com (=?utf-8?B?VGVzbGEgWmhhbmc=?=) Date: Sat, 24 Sep 2022 01:29:46 -0400 Subject: Assertion error in javac is only fixed in jdk20 Message-ID: Hi, https://bugs.openjdk.org/browse/JDK-8292756 is a severe bug of javac that prevents basic usages of record patterns. Is it possible to get the fix back ported to JDK 19? Regards, Tesla -------------- next part -------------- An HTML attachment was scrubbed... URL: From tjmnkrajyej at gmail.com Sat Sep 24 18:40:58 2022 From: tjmnkrajyej at gmail.com (tjmnkrajyej at gmail.com) Date: Sat, 24 Sep 2022 19:40:58 +0100 Subject: Bad exhaustive test using Record Pattern as delivered in OpenJDK 19 preview In-Reply-To: <38C1A33E-9DA5-4794-A807-E6D9371AA973@hxcore.ol> References: <38C1A33E-9DA5-4794-A807-E6D9371AA973@hxcore.ol> Message-ID: <3a6998f1-5985-6efa-4dc2-98c585fb0bde@gmail.com> I see. I apologize for my ignorant comment. After some experimentation, I've discovered that the switch compiles when the component of Some (value) is destructured with the type Object instead of String. Since we currently don't have generic reification, any Option can be cast to Option successfully at runtime ((Option) (Object) optionString); which means that the error that you have shown is probably for preventing cases like the one below. Option option = new Some(new Object()); ??? switch (option) { ??? ??? case Some(var value) -> System.out.println(value); ??? ??? case None() -> System.out.println("none"); ??? } Additionally, the compiler seems to deem the following code snippet valid. Option option = new Some("some"); ??? switch (option) { ??? ??? case Some(var value) -> System.out.println(value); ??? ??? case None() -> System.out.println("none"); ??? } The interesting thing about it is that the type argument in the case of None appears to be insignificant to the compiler so long as it's assignable to String. > Hello sir, > > Thank you for your attention. > > As I recall, ??switch?? without explicit null cases do not accept nulls. > > I enriched the failing cases here and included commands ran and their > output?: https://gist.github.com/grimly/1cc1228feacb51eaef59bfea86d2add5 > > In the gist above, the ??TestFailing?? file throws as I would expect > by the missing null case. > > Adding the null case in ??TestFailing2?? didn?t help either. > > Best regards, > > Michel > > *From: *tjmnkrajyej at gmail.com > *Sent: *vendredi 23 septembre 2022 17:55 > *To: *Michel Turpin ; > amber-dev at openjdk.org > *Subject: *Bad exhaustive test using Record Pattern as delivered in > OpenJDK 19 preview > > `opt` can be `null`. > > Hello, > > First, please excuse me for the way I send this bug report. I > cannot find a way to report it in other ways. > > Looking at the description of JEP 405, it made me think of Rust > enums and pattern matching. > > I tried then to play on how to write a copy of the Rust "Option" > enum : https://gist.github.com/grimly/55d414f0cc3395e87a7c813176c50ac9 > > But then I encountered this issue when narrowing the types I use > and in this particular instance ( > https://gist.github.com/grimly/55d414f0cc3395e87a7c813176c50ac9#file-testfailing-java-L9-L12 > ), the compiler throws an error and reports the switch is not > exhaustive. > > Given in my code "Option" is sealed and may only be "Some" or > "None", I would expect listing both options to make my cases > exhaustive. > > Adding a default case, I also fail to produce an instance that > would trigger it. > > Best regards, > > Michel > > > -- > > Michel TURPIN > -------------- next part -------------- An HTML attachment was scrubbed... URL: From tjmnkrajyej at gmail.com Sat Sep 24 19:12:03 2022 From: tjmnkrajyej at gmail.com (tjmnkrajyej at gmail.com) Date: Sat, 24 Sep 2022 20:12:03 +0100 Subject: Bad exhaustive test using Record Pattern as delivered in OpenJDK 19 preview In-Reply-To: <3a6998f1-5985-6efa-4dc2-98c585fb0bde@gmail.com> References: <38C1A33E-9DA5-4794-A807-E6D9371AA973@hxcore.ol> <3a6998f1-5985-6efa-4dc2-98c585fb0bde@gmail.com> Message-ID: Despite what I said in my last mail, the first switch that I showed augmented with a default case seems to execute the Some case anyway?which produces a ClassCastException?so the default case seems to be redundant during runtime albeit required during compilation. Option option = new Some(new Object()); ??? switch (option) { ??? ??? case Some(var value) -> System.out.println(value); ??? ??? case None() -> System.out.println("none"); ??? ??? case default -> System.out.println(option); ??? } So I agree with you: there seems to be a bug. > I see. I apologize for my ignorant comment. After some > experimentation, I've discovered that the switch compiles when the > component of Some (value) is destructured with the type Object instead > of String. Since we currently don't have generic reification, any > Option can be cast to Option successfully at runtime > ((Option) (Object) optionString); which means that the error > that you have shown is probably for preventing cases like the one below. > > Option option = new Some(new Object()); > ??? switch (option) { > ??? ??? case Some(var value) -> System.out.println(value); > ??? ??? case None() -> System.out.println("none"); > ??? } > > Additionally, the compiler seems to deem the following code snippet valid. > > Option option = new Some("some"); > ??? switch (option) { > ??? ??? case Some(var value) -> System.out.println(value); > ??? ??? case None() -> System.out.println("none"); > ??? } > > The interesting thing about it is that the type argument in the case > of None appears to be insignificant to the compiler so long as it's > assignable to String. > >> Hello sir, >> >> Thank you for your attention. >> >> As I recall, ??switch?? without explicit null cases do not accept nulls. >> >> I enriched the failing cases here and included commands ran and their >> output?: https://gist.github.com/grimly/1cc1228feacb51eaef59bfea86d2add5 >> >> In the gist above, the ??TestFailing?? file throws as I would expect >> by the missing null case. >> >> Adding the null case in ??TestFailing2?? didn?t help either. >> >> Best regards, >> >> Michel >> >> *From: *tjmnkrajyej at gmail.com >> *Sent: *vendredi 23 septembre 2022 17:55 >> *To: *Michel Turpin ; >> amber-dev at openjdk.org >> *Subject: *Bad exhaustive test using Record Pattern as delivered in >> OpenJDK 19 preview >> >> `opt` can be `null`. >> >> Hello, >> >> First, please excuse me for the way I send this bug report. I >> cannot find a way to report it in other ways. >> >> Looking at the description of JEP 405, it made me think of Rust >> enums and pattern matching. >> >> I tried then to play on how to write a copy of the Rust "Option" >> enum : >> https://gist.github.com/grimly/55d414f0cc3395e87a7c813176c50ac9 >> >> But then I encountered this issue when narrowing the types I use >> and in this particular instance ( >> https://gist.github.com/grimly/55d414f0cc3395e87a7c813176c50ac9#file-testfailing-java-L9-L12 >> ), the compiler throws an error and reports the switch is not >> exhaustive. >> >> Given in my code "Option" is sealed and may only be "Some" or >> "None", I would expect listing both options to make my cases >> exhaustive. >> >> Adding a default case, I also fail to produce an instance that >> would trigger it. >> >> Best regards, >> >> Michel >> >> >> -- >> >> Michel TURPIN >> -------------- next part -------------- An HTML attachment was scrubbed... URL: From ice1000kotlin at foxmail.com Mon Sep 26 23:18:24 2022 From: ice1000kotlin at foxmail.com (=?utf-8?B?VGVzbGEgWmhhbmc=?=) Date: Mon, 26 Sep 2022 19:18:24 -0400 Subject: Mixing record patterns and literal/enum patterns? Message-ID: Hi,  Is it currently possible/planned to have mixed literal or enum patterns? E.g.,  enum E { a, b } record R(E e) {} And use the pattern case R(E.a) something something Regards, Tesla -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Tue Sep 27 05:55:20 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 27 Sep 2022 07:55:20 +0200 (CEST) Subject: Mixing record patterns and literal/enum patterns? In-Reply-To: References: Message-ID: <2114293534.13619512.1664258120313.JavaMail.zimbra@u-pem.fr> > From: "Tesla Ice Zhang" > To: "amber-dev" > Sent: Tuesday, September 27, 2022 1:18:24 AM > Subject: Mixing record patterns and literal/enum patterns? > Hi, > Is it currently possible/planned to have mixed literal or enum patterns? E.g., > enum E { a, b } > record R(E e) {} > And use the pattern > case R(E.a) something something It's what we call a constant pattern. The syntax is still in flux. Given that a switch on an enum does not require the class of the enum constant to be specified, another possible syntax may be case R(a), or we may want a quoting mechanism for the constants, case R('E.a) or case R(== E.a). Anyway, you can already write case R(E e) when e == E.a. > Regards, > Tesla regards, R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From duke at openjdk.org Wed Sep 28 17:55:50 2022 From: duke at openjdk.org (duke) Date: Wed, 28 Sep 2022 17:55:50 GMT Subject: git: openjdk/amber-docs: Create on-ramp.md Message-ID: <8721cd20-d7b9-4259-bdb3-36f1a7239458@openjdk.org> Changeset: 8d11ceba Author: Brian Goetz Committer: GitHub Date: 2022-09-28 13:55:09 +0000 URL: https://git.openjdk.org/amber-docs/commit/8d11ceba6921c7de1f4fa60e3f6fde352e0d66b8 Create on-ramp.md + site/design-notes/on-ramp.md From duke at openjdk.org Wed Sep 28 18:48:11 2022 From: duke at openjdk.org (duke) Date: Wed, 28 Sep 2022 18:48:11 GMT Subject: git: openjdk/amber-docs: Update on-ramp.md Message-ID: <6d0dc322-a2e2-419d-b7c1-2ce181e67684@openjdk.org> Changeset: f44b154c Author: Brian Goetz Committer: GitHub Date: 2022-09-28 14:45:28 +0000 URL: https://git.openjdk.org/amber-docs/commit/f44b154c1f186dbfd677abbd7380908cd412a75c Update on-ramp.md ! site/design-notes/on-ramp.md From duke at openjdk.org Wed Sep 28 19:35:06 2022 From: duke at openjdk.org (duke) Date: Wed, 28 Sep 2022 19:35:06 GMT Subject: git: openjdk/amber-docs: Update on-ramp.md Message-ID: <02364265-79b3-41eb-b588-c60e098b607d@openjdk.org> Changeset: 97c7649d Author: Brian Goetz Committer: GitHub Date: 2022-09-28 15:34:38 +0000 URL: https://git.openjdk.org/amber-docs/commit/97c7649d984ac8d4d4c3660e8f5c898a69468665 Update on-ramp.md ! site/design-notes/on-ramp.md From forax at univ-mlv.fr Fri Sep 30 11:59:04 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 30 Sep 2022 13:59:04 +0200 (CEST) Subject: exhaustive switch statement and return Message-ID: <1641604373.16323418.1664539118909.JavaMail.zimbra@u-pem.fr> Hi all, this code does not compile but i think it should. public class Example { sealed interface Action { record PrintAction() implements Action {} record DeleteAction() implements Action {} } public int foo(Action action) { switch (action) { case Action.PrintAction printAction -> { return 1; } case Action.DeleteAction deleteAction -> { return 2; } } } } regards, R?mi From jan.lahoda at oracle.com Fri Sep 30 12:20:35 2022 From: jan.lahoda at oracle.com (Jan Lahoda) Date: Fri, 30 Sep 2022 14:20:35 +0200 Subject: exhaustive switch statement and return In-Reply-To: <1641604373.16323418.1664539118909.JavaMail.zimbra@u-pem.fr> References: <1641604373.16323418.1664539118909.JavaMail.zimbra@u-pem.fr> Message-ID: <978d6dfe-732b-2bfb-1c9e-268ea43ef705@oracle.com> Thanks, I've filled: https://bugs.openjdk.org/browse/JDK-8294670 Jan On 30. 09. 22 13:59, Remi Forax wrote: > Hi all, > this code does not compile but i think it should. > > public class Example { > sealed interface Action { > record PrintAction() implements Action {} > record DeleteAction() implements Action {} > } > > > public int foo(Action action) { > switch (action) { > case Action.PrintAction printAction -> { > return 1; > } > case Action.DeleteAction deleteAction -> { > return 2; > } > } > } > } > > regards, > R?mi From duke at openjdk.org Fri Sep 30 18:53:41 2022 From: duke at openjdk.org (duke) Date: Fri, 30 Sep 2022 18:53:41 GMT Subject: git: openjdk/amber-docs: Update on-ramp.md (#18) Message-ID: <434ad2e6-4920-4b2e-8c82-12a15b02a4d6@openjdk.org> Changeset: b0708c76 Author: Mark Reinhold Committer: GitHub Date: 2022-09-30 11:53:14 +0000 URL: https://git.openjdk.org/amber-docs/commit/b0708c7653a41a9c93794c53c426edd3d0c02f4e Update on-ramp.md (#18) ! site/design-notes/on-ramp.md