From forax at univ-mlv.fr Wed Jan 5 23:49:52 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 6 Jan 2022 00:49:52 +0100 (CET) Subject: Spring, Quarkus and Java records Message-ID: <392560826.6000529.1641426592860.JavaMail.zimbra@u-pem.fr> Hi all, I've cut and paste several tutorials explaining how to use Spring Boot and Quarkus (the RedHat metoo) to implement a REST app server and try to see where it can be interesting to use Java records instead of classical Java classes. For me, records can be used at two locations, - as parameter/return type of the REST controller method to decode/encode JSON, so users have a better control on the JSON emitted. It's important because it becomes easy to filter out a key and to control the order of the keys of the JSON objects. - a the REST controller itself, because it forces the user to only use constructor injection (and not setter/field injection) and it also avoid user to create fields in the controller forgetting to add the proper synchronization. For Spring, - Obviously, a JPA entity can not be a record because JPA/Hibernate requires an entity to be mutable. - Jackson supports records, so parameter and return type of the REST controller methods can use records. - Sadly, declaring a REST controller as a record is not supported, because the implementation of @Transactionnal creates a subclass. For Spring reactive, - Spring Reactive do not use JPA or Reactive Hibernate but R2DBC, and R2DBC supports records so mutable entities anymore, yai ! For Quarkus, - like with Spring, Quarkus uses Jackson so the parameter and return type of the methods of the REST controller can use records. - the controller can be a record too, Quarkus change the bytecode to support @Transactional, so no subclass needed. For Quarkus reactive, Quarkus does not provide any reactive implementation for embedded databases like H2, so i was not able to test properly that combination. The code is here: https://github.com/forax/webapp-java17 regards, R?mi PS: I also know that JOOQ and Jdbi supports records. From duke at openjdk.java.net Thu Jan 6 22:08:56 2022 From: duke at openjdk.java.net (J.Duke) Date: Thu, 6 Jan 2022 22:08:56 GMT Subject: [stats-before-this-super] RFR: Merge master Message-ID: Hi all, this is an _automatically_ generated pull request to notify you that there are 39 commits from the branch `master`that can **not** be merged into the branch `stats-before-this-super`: The following file contains merge conflicts: - .jcheck/conf All Committers in this [project](https://openjdk.java.net/census#amber) have access to my [personal fork](https://github.com/openjdk-bot/amber) and can therefore help resolve these merge conflicts (you may want to coordinate who should do this). The following paragraphs will give an example on how to solve these merge conflicts and push the resulting merge commit to this pull request. The below commands should be run in a local clone of your [personal fork](https://wiki.openjdk.java.net/display/skara#Skara-Personalforks) of the [openjdk/amber](https://github.com/openjdk/amber) repository. # Ensure target branch is up to date $ git checkout stats-before-this-super $ git pull https://github.com/openjdk/amber.git stats-before-this-super # Fetch and checkout the branch for this pull request $ git fetch https://github.com/openjdk-bot/amber.git +80:openjdk-bot-80 $ git checkout openjdk-bot-80 # Merge the target branch $ git merge stats-before-this-super When you have resolved the conflicts resulting from the `git merge` command above, run the following commands to create a merge commit: $ git add paths/to/files/with/conflicts $ git commit -m 'Merge master' When you have created the merge commit, run the following command to push the merge commit to this pull request: $ git push https://github.com/openjdk-bot/amber.git openjdk-bot-80:80 _Note_: if you are using SSH to push commits to GitHub, then change the URL in the above `git push` command accordingly. Thanks, J. Duke ------------- Commit messages: - 4884570: StreamPrintService.isAttributeValueSupported does not work properly for SheetCollate - 8273322: Enhance macro logic optimization for masked logic operations. - 8279505: Update documentation for RETRY_COUNT and REPEAT_COUNT - 8279339: (ch) Input/Output streams returned by Channels factory methods don't support concurrent read/write ops - 8211004: javac is complaining about non-denotable types and refusing to generate the class file - Merge - 8279529: ProblemList java/nio/channels/DatagramChannel/ManySourcesAndTargets.java on macosx-aarch64 - 8278612: [macos] test/jdk/java/awt/dnd/RemoveDropTargetCrashTest crashes with VoiceOver on macOS - 8279525: ProblemList java/awt/GraphicsDevice/CheckDisplayModes.java on macosx-aarch64 - 8278897: Alignment of heap segments is not enforced correctly - ... and 29 more: https://git.openjdk.java.net/amber/compare/299022df...b3dbfc64 The webrev contains the conflicts with stats-before-this-super: - merge conflicts: https://webrevs.openjdk.java.net/?repo=amber&pr=78&range=00.conflicts Changes: https://git.openjdk.java.net/amber/pull/78/files Stats: 4400 lines in 113 files changed: 3732 ins; 408 del; 260 mod Patch: https://git.openjdk.java.net/amber/pull/78.diff Fetch: git fetch https://git.openjdk.java.net/amber pull/78/head:pull/78 PR: https://git.openjdk.java.net/amber/pull/78 From brian.goetz at oracle.com Wed Jan 12 14:54:32 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 12 Jan 2022 09:54:32 -0500 Subject: Switch on sealed enums In-Reply-To: References: Message-ID: (delayed response) Your issue (1) is most of the problem; enum switches have always been special, and so accomodating enums in general switches will be some more work.? But, once we do so, there is no major impediment to saying the merged switch covers Token; coverage on a sealed type is derived from coverage on all the permitted subtypes. Still, I think this is a lower priority than a number of other pattern-related things; it makes up for some of the shortcomings of enums, but still leaves a lot of them on the table. On 11/24/2021 3:23 AM, Matthias Perktold - ASA wrote: > I've played around with sealed interfaces implemented by multiple enums only, and I've found some inconveniences I wanted to share here. > > Consider the following class hierarchy: > > sealed interface Token { > enum Digit implements Token { ONE, TWO, THREE } > enum Letter implements Token { A, B, C } > } > > I can use a nested switch expression to cover all cases: > > static String toStringNested(Token token) { > return switch (token) { > case Digit d -> switch (d) { > case ONE -> "1"; > case TWO -> "2"; > case THREE -> "3"; > }; > case Letter l -> switch (l) { > case A -> "A"; > case B -> "B"; > case C -> "C"; > }; > }; > } > > I thought that in theory, I should be able to use a single, flattened switch that covers all digits and all letters: > > static String toStringFlat(Token token) { > return switch (token) { > case ONE -> "1"; > case TWO -> "2"; > case THREE -> "3"; > case A -> "A"; > case B -> "B"; > case C -> "C"; > }; > } > > This is probably deliberately not supported (yet). Anyway, here are my findings: > > 1. The compiler requires that the enum constants be unqualified. > But since we have multiple enums here, just leaving the enum away doesn't work either; we need to add static imports: > > import static package.to.Token.Digit.*; > import static package.to.Token.Letter.*; > > 2. The compiler cannot figure out that the switch is exhaustive, even if we do cover all cases. > We need to add a default case to fix this compiler error. > > 3. With the default case added, the code still doesn't compile (my IDE thinks it does); > We get an error "constant expression required" for each case. > > > In case you wonder why one would use sealed hierarchies of enums, it probably makes more sense to think about these > as "enums with subgroups". Using this approach, you can model more precisely that a method accepts or returns only a subset > of the cases, but a logical coherent subset. > One special reason of grouping enum constants is the use of generics: we could extend our hierarchy as follows: > > sealed interface Token { > T value(); > enum Digit implements Token { > ONE, TWO, THREE; > @Override public Integer value() { ... } > } > enum Letter implements Token { > A, B, C; > @Override public Character value() { ... } > } > } > > This could also be seen as a workaround for JEP 301: "Enhanced Enums", but a very good one in my opinion. > Only when every enum constant needs a different type argument (as in the JEP's examples), you would need to wrap each constant > in its own enum. > > Overall, I think there are good places for such groupings of enums. And it would help if they could be used just like a single enum inside > switch. > > > Cheers, > Matthias Perktold > From brian.goetz at oracle.com Wed Jan 12 14:56:30 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 12 Jan 2022 09:56:30 -0500 Subject: Final variable initialization problem with exhaustive switch In-Reply-To: References: Message-ID: <8d7bd4b3-d6b3-30fd-d036-1357435b919a@oracle.com> I'm not seeing this fail on JDK 17. On 11/29/2021 3:04 PM, Dimitris Paltatzidis wrote: > Consider the case: > > enum Parity {ODD, EVEN} > Parity p = ... > final int a; > switch (p) { > case Parity.ODD -> a = 1; > case Parity.EVEN -> a = 0; > } > int b = a + 1; //Compile time error: variable a might not have been > initialized > > The switch is exhaustive, yet we need to add a default case to compile. The > same behaviour is observed with instance final fields too. > Without changing the semantics, the code below compiles: > > final int a = switch (p) { > case Parity.ODD -> 1; > case Parity.EVEN -> 0; > }; > int b = a + 1; //Compiles > > How can the compiler prove that the final variable will be initialized only > in the second case and not in the first too? > > Thanks for your time. From dcrystalmails at gmail.com Wed Jan 12 20:35:17 2022 From: dcrystalmails at gmail.com (Dimitris Paltatzidis) Date: Wed, 12 Jan 2022 22:35:17 +0200 Subject: Final variable initialization problem with exhaustive switch In-Reply-To: <8d7bd4b3-d6b3-30fd-d036-1357435b919a@oracle.com> References: <8d7bd4b3-d6b3-30fd-d036-1357435b919a@oracle.com> Message-ID: The file I have is: Main.java public class Main { public static void main(String[] args) { enum Parity {ODD, EVEN} Parity p = Math.random() > 0.5 ? Parity.EVEN : Parity.ODD; //or whatever final int a; switch (p) { case ODD -> a = 1; case EVEN -> a = 0; } int b = a + 1; } } Trying to compile it with (java -version): openjdk version "17.0.1" 2021-10-19 OpenJDK Runtime Environment (build 17.0.1+12-39) OpenJDK 64-Bit Server VM (build 17.0.1+12-39, mixed mode, sharing) I get: Main.java:11: error: variable a might not have been initialized int b = a + 1; ^ 1 error I tried it in IntelliJ as well, and aside from the pre-compile warnings I got, I still could not compile it. Should I enable preview features to pass? I think I might have done that too, but got the same results. I'm confused. Suppose I'm doing something wrong, does that mean switch statements can be total in JDK 17? ???? ??? 12 ??? 2022 ???? 4:56 ?.?., ?/? Brian Goetz ??????: > I'm not seeing this fail on JDK 17. > > On 11/29/2021 3:04 PM, Dimitris Paltatzidis wrote: > > Consider the case: > > enum Parity {ODD, EVEN} > Parity p = ... > final int a; > switch (p) { > case Parity.ODD -> a = 1; > case Parity.EVEN -> a = 0; > } > int b = a + 1; //Compile time error: variable a might not have been > initialized > > The switch is exhaustive, yet we need to add a default case to compile. The > same behaviour is observed with instance final fields too. > Without changing the semantics, the code below compiles: > > final int a = switch (p) { > case Parity.ODD -> 1; > case Parity.EVEN -> 0; > }; > int b = a + 1; //Compiles > > How can the compiler prove that the final variable will be initialized only > in the second case and not in the first too? > > Thanks for your time. > > > From pedro.lamarao at prodist.com.br Wed Jan 12 20:55:17 2022 From: pedro.lamarao at prodist.com.br (=?UTF-8?Q?Pedro_Lamar=C3=A3o?=) Date: Wed, 12 Jan 2022 17:55:17 -0300 Subject: Final variable initialization problem with exhaustive switch In-Reply-To: <8d7bd4b3-d6b3-30fd-d036-1357435b919a@oracle.com> References: <8d7bd4b3-d6b3-30fd-d036-1357435b919a@oracle.com> Message-ID: This is what I got: PS E:\> E:\opt\jdk-17+35\bin\java --version openjdk 17 2021-09-14 OpenJDK Runtime Environment Temurin-17+35 (build 17+35) OpenJDK 64-Bit Server VM Temurin-17+35 (build 17+35, mixed mode, sharing) PS E:\> E:\opt\jdk-17+35\bin\javac --release 17 --enable-preview Main.java Main.java:11: error: variable a might not have been initialized int b = a + 1; ^ 1 error Regards, Pedro. From romanowski.mateusz at gmail.com Wed Jan 12 21:22:42 2022 From: romanowski.mateusz at gmail.com (Mateusz Romanowski) Date: Wed, 12 Jan 2022 22:22:42 +0100 Subject: Final variable initialization problem with exhaustive switch In-Reply-To: References: <8d7bd4b3-d6b3-30fd-d036-1357435b919a@oracle.com> Message-ID: Hi Dimitris et al., I believe there is confusion on whether we are talking about switch statement or switch expression. If I assign value of this `switch` to a variable, it compiles: ``` final int a; Object ignored = switch (p) { case ODD -> a = 1; case EVEN -> a = 0; }; int b = a + 1; ``` Otherwise, "error: variable a might not have been initialized" is shown. Cheers, Mateusz On Wed, Jan 12, 2022 at 10:19 PM Pedro Lamar?o wrote: > This is what I got: > > PS E:\> E:\opt\jdk-17+35\bin\java --version > openjdk 17 2021-09-14 > OpenJDK Runtime Environment Temurin-17+35 (build 17+35) > OpenJDK 64-Bit Server VM Temurin-17+35 (build 17+35, mixed mode, sharing) > PS E:\> E:\opt\jdk-17+35\bin\javac --release 17 --enable-preview Main.java > Main.java:11: error: variable a might not have been initialized > int b = a + 1; > ^ > 1 error > > Regards, > Pedro. > From forax at univ-mlv.fr Wed Jan 12 22:21:15 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 12 Jan 2022 23:21:15 +0100 (CET) Subject: Final variable initialization problem with exhaustive switch In-Reply-To: References: <8d7bd4b3-d6b3-30fd-d036-1357435b919a@oracle.com> Message-ID: <1355709946.10892187.1642026075914.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Mateusz Romanowski" > To: "Dimitris Paltatzidis" > Cc: "amber-dev" > Sent: Wednesday, January 12, 2022 10:22:42 PM > Subject: Re: Final variable initialization problem with exhaustive switch > Hi Dimitris et al., > I believe there is confusion on whether we are talking about switch > statement or switch expression. > > If I assign value of this `switch` to a variable, it compiles: > ``` > final int a; > Object ignored = switch (p) { > case ODD -> a = 1; > case EVEN -> a = 0; > }; > int b = a + 1; > ``` > > Otherwise, "error: variable a might not have been initialized" is shown. yes, Mateusz is right, with a switch expression the compiler insert a default that throw an exception, while with a switch statement, the old switch, the compiler insert a default that does nothing. This code compiles because it's a switch expression final int a; Object ignored = switch (p) { case ODD -> a = 1; case EVEN -> a = 0; // the compiler adds a default that throws an exception }; and this code compiles because it's a switch statement with a default final int a; switch (p) { case ODD -> a = 1; case EVEN -> a = 0; default -> throw null; } We (the expert group) have discussed about adding a warning for the switch statement asking for a default case but it has no been implemented. > > Cheers, > Mateusz regards, R?mi > > > On Wed, Jan 12, 2022 at 10:19 PM Pedro Lamar?o > wrote: > >> This is what I got: >> >> PS E:\> E:\opt\jdk-17+35\bin\java --version >> openjdk 17 2021-09-14 >> OpenJDK Runtime Environment Temurin-17+35 (build 17+35) >> OpenJDK 64-Bit Server VM Temurin-17+35 (build 17+35, mixed mode, sharing) >> PS E:\> E:\opt\jdk-17+35\bin\javac --release 17 --enable-preview Main.java >> Main.java:11: error: variable a might not have been initialized >> int b = a + 1; >> ^ >> 1 error >> >> Regards, >> Pedro. From nlisker at gmail.com Tue Jan 18 19:25:50 2022 From: nlisker at gmail.com (Nir Lisker) Date: Tue, 18 Jan 2022 21:25:50 +0200 Subject: No subject Message-ID: Hi, In JavaFX we have the following exported interface: public interface ObsevableValue { void addListener(InvalidationListener listener); void removeListener(InvalidationListener listener); void addListener(ChangeListener listener); void removeListener(ChangeListener listener); } and many exported classes that implement it in the following way: public ObjectBinding extends ObjectExpression implements ObsevableValue { private ExpressionHelper helper = null; @Override public void addListener(InvalidationListener listener) { helper = ExpressionHelper.addListener(helper, this, listener); } @Override public void removeListener(InvalidationListener listener) { helper = ExpressionHelper.removeListener(helper, listener); } @Override public void addListener(ChangeListener listener) { helper = ExpressionHelper.addListener(helper, this, listener); } @Override public void removeListener(ChangeListener listener) { helper = ExpressionHelper.removeListener(helper, listener); } Where ExpressionHelper is some internal helper class. I'm looking at removing the code duplication by extracting this behavior and having all classes use the same code. My first Idea was to create a non-exported (internal) interface that has a default implementation to the methods and have the clases implement them: public interface ObservableValueHelper extends ObservableValue { ExpressionHelper getHelper(); void setHelper(ExpressionHelper helper); @Override public default void addListener(InvalidationListener listener) { var expressionHelper = helperSupplier().get(); setHelper(ExpressionHelper.addListener(getHelper(), this, listener)); } @Override public default void removeListener(InvalidationListener listener) { setHelper(ExpressionHelper.removeListener(getHelper(), listener)); } @Override public default void addListener(ChangeListener listener) { setHelper(ExpressionHelper.addListener(getHelper(), this, listener)); } @Override public default void removeListener(ChangeListener listener) { setHelper(ExpressionHelper.removeListener(getHelper(), listener)); } } and now: public class ObjectBinding extends ObjectExpression implements ObservableValueHelper { @Override public ExpressionHelper getHelper() { return helper; } @Override public void setHelper(ExpressionHelper helper) { this.helper = helper; } } but this can't work because the new public methods will be exposed API. I'm also only saving half the work because now I need to declare 2 methods to compensate, but I can envision a case where the interface has more than 4 methods. I feel like I am stuck between several rocks and hard places: * If I extract the behavior to an interface like I showed, I can't also extract the field because interfaces can only hold static fields. * If I extract the behavior to a class, I can extract the field too, but I can't extend it since I'm already extending another class. * If I use the interface approach with the get/set abstract methods, I have to declare them as public. I can't reduce their visibility in the implementing class, and I can't not export them. * Composition instead of inheritance isn't viable because I have to provide implementation for the abstract methods somewhere in the hierarchy. I'm not advocating for multiple inheritance or new visibility modifiers (though here there is a broader issue where library designers want a "protected but not public API" kind of visibilit), but I would like to not need to copy-paste the same code everywhere, whatever the solution might be. Is there a solution? - Nir From brian.goetz at oracle.com Tue Jan 18 19:51:54 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 18 Jan 2022 14:51:54 -0500 Subject: No subject In-Reply-To: References: Message-ID: <5fc8c100-eb55-c20f-daf8-84b53044cecc@oracle.com> The full range of accessibilities for interface methods was discussed at some length during the design process.? Interfaces were extended to support private methods, which leaves questions hanging over whether protected and package-private methods could also be supported in interfaces.? (Also, it's limiting that private fields/classes are not permitted in interfaces, which would be very useful, and this was mostly an oversight, which will probably be fixed eventually.)? What you want -- protected methods in interfaces -- are more complicated than it might first appear, because the semantics of "protected" are also more complicated than they first appear.? It is probably possible but would surely be a lot of incremental complexity for not as much incremental expressivity. On 1/18/2022 2:25 PM, Nir Lisker wrote: > I'm not advocating for multiple inheritance or new visibility modifiers > (though here there is a broader issue where library designers want a > "protected but not public API" kind of visibilit), but I would like to not > need to copy-paste the same code everywhere, whatever the solution might > be. Is there a solution? From nlisker at gmail.com Tue Jan 18 20:03:59 2022 From: nlisker at gmail.com (Nir Lisker) Date: Tue, 18 Jan 2022 22:03:59 +0200 Subject: Limitation of Interfaces In-Reply-To: <5fc8c100-eb55-c20f-daf8-84b53044cecc@oracle.com> References: <5fc8c100-eb55-c20f-daf8-84b53044cecc@oracle.com> Message-ID: (Sorry about forgetting the subject line, I added it now) Actually protected methods wouldn't save me either since they are part of the public API. I either need "protected but not public API" or package-private, which is less suitable but probably easier. I think that in this case private fields in interfaces will be more ideal. Good to know that this is something that can be changed in the future, even if it's not on the drawing table yet. On Tue, Jan 18, 2022 at 9:52 PM Brian Goetz wrote: > The full range of accessibilities for interface methods was discussed at > some length during the design process. Interfaces were extended to support > private methods, which leaves questions hanging over whether protected and > package-private methods could also be supported in interfaces. (Also, it's > limiting that private fields/classes are not permitted in interfaces, which > would be very useful, and this was mostly an oversight, which will probably > be fixed eventually.) What you want -- protected methods in interfaces -- > are more complicated than it might first appear, because the semantics of > "protected" are also more complicated than they first appear. It is > probably possible but would surely be a lot of incremental complexity for > not as much incremental expressivity. > > > > > > On 1/18/2022 2:25 PM, Nir Lisker wrote: > > I'm not advocating for multiple inheritance or new visibility modifiers > (though here there is a broader issue where library designers want a > "protected but not public API" kind of visibilit), but I would like to not > need to copy-paste the same code everywhere, whatever the solution might > be. Is there a solution? > > > From brian.goetz at oracle.com Tue Jan 18 20:13:56 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 18 Jan 2022 15:13:56 -0500 Subject: [External] : Limitation of Interfaces In-Reply-To: References: <5fc8c100-eb55-c20f-daf8-84b53044cecc@oracle.com> Message-ID: <76a77b47-ce0e-066b-5cc1-61df6ae74a2c@oracle.com> Don't forget that interface fields are always static, so this may not help as much as you think. On 1/18/2022 3:03 PM, Nir Lisker wrote: > (Sorry about forgetting the subject line, I added it now) > > Actually protected methods wouldn't save me either since they are part > of the public API. I either need "protected but not public API" or > package-private, which is less suitable but probably easier. I think > that in this case private fields in interfaces will be more ideal. > Good to know that this is something that can be changed in the future, > even if it's not on the drawing table yet. > > On Tue, Jan 18, 2022 at 9:52 PM Brian Goetz > wrote: > > The full range of accessibilities for interface methods was > discussed at some length during the design process.? Interfaces > were extended to support private methods, which leaves questions > hanging over whether protected and package-private methods could > also be supported in interfaces.? (Also, it's limiting that > private fields/classes are not permitted in interfaces, which > would be very useful, and this was mostly an oversight, which will > probably be fixed eventually.) What you want -- protected methods > in interfaces -- are more complicated than it might first appear, > because the semantics of "protected" are also more complicated > than they first appear.? It is probably possible but would surely > be a lot of incremental complexity for not as much incremental > expressivity. > > > > > > On 1/18/2022 2:25 PM, Nir Lisker wrote: >> I'm not advocating for multiple inheritance or new visibility modifiers >> (though here there is a broader issue where library designers want a >> "protected but not public API" kind of visibilit), but I would like to not >> need to copy-paste the same code everywhere, whatever the solution might >> be. Is there a solution? > > From federico.peralta at gmail.com Tue Jan 18 21:52:39 2022 From: federico.peralta at gmail.com (Fede Peralta) Date: Tue, 18 Jan 2022 18:52:39 -0300 Subject: [External] : Limitation of Interfaces In-Reply-To: <76a77b47-ce0e-066b-5cc1-61df6ae74a2c@oracle.com> References: <5fc8c100-eb55-c20f-daf8-84b53044cecc@oracle.com> <76a77b47-ce0e-066b-5cc1-61df6ae74a2c@oracle.com> Message-ID: Hi Nir, I think it would be useful to know what real problem you're trying to solve. I'm almost sure there's a way to achieve what you want with what the language offers now. I remember that I once needed to implement an interface with callback methods, but I didn't want these callback methods to be public. The solution was already available in some issue of the javaspecialists newsletter and was something like this (simplified): public interface A { void method(); } public class AImpl { private SomeoneThatNeedsMyCallback handler; private String someField; // etc private final A inner = new A() { public void method() { // real implementation goes here // modifies AImpl.this.someField and other state // performs side-effect actions, etc } } // Non-public constructor AImpl() { this.handler = new SomeoneThatNeedsMyCallback(inner); } // Non-public, can even be private void method() { // delegates to inner.method() inner.method(); } } You might also need that AImpl actually implements A. In this case, make it so, but also make the public method method() a no-op, i.e. without implementation and, if needed, add a new non-public method to AImpl, i.e. safeMethod() that delegates to inner.method() The code becomes more compact if you use lambdas and/or method references. Disclaimer: I'm not from Oracle, just a guy that loves this list :) Regards, Fede El mar, 18 ene 2022 a las 17:14, Brian Goetz () escribi?: > > Don't forget that interface fields are always static, so this may not > help as much as you think. > > On 1/18/2022 3:03 PM, Nir Lisker wrote: > > (Sorry about forgetting the subject line, I added it now) > > > > Actually protected methods wouldn't save me either since they are part > > of the public API. I either need "protected but not public API" or > > package-private, which is less suitable but probably easier. I think > > that in this case private fields in interfaces will be more ideal. > > Good to know that this is something that can be changed in the future, > > even if it's not on the drawing table yet. > > > > On Tue, Jan 18, 2022 at 9:52 PM Brian Goetz > > wrote: > > > > The full range of accessibilities for interface methods was > > discussed at some length during the design process. Interfaces > > were extended to support private methods, which leaves questions > > hanging over whether protected and package-private methods could > > also be supported in interfaces. (Also, it's limiting that > > private fields/classes are not permitted in interfaces, which > > would be very useful, and this was mostly an oversight, which will > > probably be fixed eventually.) What you want -- protected methods > > in interfaces -- are more complicated than it might first appear, > > because the semantics of "protected" are also more complicated > > than they first appear. It is probably possible but would surely > > be a lot of incremental complexity for not as much incremental > > expressivity. > > > > > > > > > > > > On 1/18/2022 2:25 PM, Nir Lisker wrote: > >> I'm not advocating for multiple inheritance or new visibility modifiers > >> (though here there is a broader issue where library designers want a > >> "protected but not public API" kind of visibilit), but I would like to not > >> need to copy-paste the same code everywhere, whatever the solution might > >> be. Is there a solution? > > > > From knightblue610 at yahoo.com Fri Jan 21 15:30:01 2022 From: knightblue610 at yahoo.com (Brad Markel) Date: Fri, 21 Jan 2022 15:30:01 +0000 (UTC) Subject: Multimethods feature proposal References: <1179795615.419772.1642779001692.ref@mail.yahoo.com> Message-ID: <1179795615.419772.1642779001692@mail.yahoo.com> Greetings, First, let me start off by apologizing if I'm emailing the wrong mailing list.? I understand that the JEP process suggests emailing feature requests to another list, but since this is so closely related to the Amber development of pattern matching with sealed classes, I felt it wise to run this by the team before submitting a JEP to that list.? Consider the use of a sealed type in a switch expression as envisioned by the current second preview feature in JDK 17: ? Shape rotate(Shape shape, double angle) { ????? return switch (shape) {?? // pattern matching switch ????????? case Circle c??? -> c; ????????? case Rectangle r -> r.rotate(angle); ????????? case Square s??? -> s.rotate(angle); ????????? // no default needed! ????? } ? } This code, essentially, calls another method based on the type of its parameter as well as the type of the caller.? This is double-dispatch code, but it's written with too much ceremony; putting code in blocks of a switch statement like this encourages users to add complex logic to the switch that should be factored out: ? Shape rotate(Shape shape, double angle) { ????? return switch (shape) {?? // pattern matching switch ????????? case Circle c??? -> { ??????????? c.setColor("RED"); ??????????? c.setLine("DASHED"); ??????????? //Some other processing code... ??????????? yield c; ????????? } ????????? case Rectangle r -> { ??????????? r.setColor("RED"); ??????????? r.setLine("DASHED"); ??????????? yield r.rotate(angle); ????????? } ????????? case Square s??? -> { ??????????? s.setColor("RED"); ??????????? s.setLine("DASHED"); ??????????? yield s.rotate(angle); ????????? } ????????? // no default needed! ????? } ? } The original intent of inheritance is to bind code like this to the type, rather than adding potentially risky branching code.? This can get worse if more than one parameter is passed in: ? Shape collide(Shape shape1, Shape shape2, double angle) { ????? return switch (shape1) {?? // pattern matching switch ????????? case Circle c??? -> { ??????????? switch (shape2) { ????????????? case Circle c2 -> { ??????????????? //Some pre-processing code... ??????????????? yield findAreaOfCollisionForCircle(c, c2); ????????????? } ????????????? case Rectangle r2 -> { ??????????????? //Some pre-processing code.... ??????????????? yield findAreaOfCollisionForRectangle(c, r2); ????????????? } ????????????? case Square s2 -> { ??????????????? //Some pre-processing code.... ??????????????? yield findAreaOfCollisionForSquare(c, s2);???????????? ? ????????????? } ??????????? } ????????? } ????????? case Rectangle r -> { ??????????? switch (shape2) { ????????????? case Circle c2 -> { ??????????????? //Some pre-processing code... ??????????????? yield findAreaOfCollisionForCircle(r, c2); ????????????? } ????????????? case Rectangle r2 -> { ??????????????? //Some pre-processing code.... ??????????????? yield findAreaOfCollisionForRectangle(r, r2); ????????????? } ????????????? case Square s2 -> { ??????????????? //Some pre-processing code.... ??????????????? yield findAreaOfCollisionForSquare(r, s2);???????????? ? ????????????? } ??????????? } ????????? } ????????? case Square s??? -> { ??????????? //and so on.... ????????? } ????????? // no default needed! ????? } ? } ? Instead, the proposal would be that the compiler generates this code instead, and allow a multimethod annotation at the interface level: ? interface Shape { ??? @Multimethod ??? Shape collide(Shape shape, double angle); ? } ? class Rectangle { ??? @Multimethod ??? Shape collide(Circle shape, double angle) { ... } ??? @Multimethod ??? multi Shape collide(Rectangle shape, double angle) { ... } ??? @Multimethod ??? multi Shape collide(Square square, double angle) { ... } ? } ? class Rectangle { ??? @Multimethod ??? Shape collide(Circle shape, double angle) { ... } ??? @Multimethod ??? Shape collide(Rectangle shape, double angle) { ... } ??? @Multimethod ??? Shape collide(Square square, double angle) { ... } ? } ? class Square { ??? @Multimethod ??? Shape collide(Circle shape, double angle) { ... } ??? @Multimethod ??? Shape collide(Rectangle shape, double angle) { ... } ??? @Multimethod ??? Shape collide(Square square, double angle) { ... } ??? // no default needed! ? } This reduces the confusion of adding switch statements to auxiliary classes where class behavior depends on the interaction between two or more types.? This would extend to multiple parameters as well: ? interface Shape { ??? @Multimethod ??? Shape collideTwo(Shape shape1, Shape shape2, double angle); ? } ? class Circle { ??? @Multimethod ??? Shape collideTwo(Circle shape1, Circle shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Circle shape1, Rectangle shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Circle shape1, Square shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Rectangle shape1, Circle shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Rectangle shape1, Rectangle shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Rectangle shape1, Square shape2, double angle) { ... } ??? // ...and so on ? } ? class Rectangle { ??? @Multimethod ??? Shape collideTwo(Circle shape1, Circle shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Circle shape1, Rectangle shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Circle shape1, Square shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Rectangle shape1, Circle shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Rectangle shape1, Rectangle shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Rectangle shape1, Square shape2, double angle) { ... } ? } Thus, the branching code is reduced, and the functionality is encapsulated into methods, improving readability and reducing testing complexity.? The compiler internally can generate the complex switch statements, which is essentially the equivalent of the above. Would such a feature be worthy of consideration? Thanks for your time,Brad Markel From forax at univ-mlv.fr Fri Jan 21 16:05:39 2022 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 21 Jan 2022 17:05:39 +0100 (CET) Subject: Multimethods feature proposal In-Reply-To: <1179795615.419772.1642779001692@mail.yahoo.com> References: <1179795615.419772.1642779001692.ref@mail.yahoo.com> <1179795615.419772.1642779001692@mail.yahoo.com> Message-ID: <1008331739.1891139.1642781139513.JavaMail.zimbra@u-pem.fr> ----- Original Message ----- > From: "Brad Markel" > To: "amber-dev" > Sent: Friday, January 21, 2022 4:30:01 PM > Subject: Multimethods feature proposal > Greetings, > First, let me start off by apologizing if I'm emailing the wrong mailing list. > ? I understand that the JEP process suggests emailing feature requests to > another list, but since this is so closely related to the Amber development of > pattern matching with sealed classes, I felt it wise to run this by the team > before submitting a JEP to that list. > > Consider the use of a sealed type in a switch expression as envisioned by the > current second preview feature in JDK 17: > >? Shape rotate(Shape shape, double angle) { >????? return switch (shape) {?? // pattern matching switch >????????? case Circle c??? -> c; >????????? case Rectangle r -> r.rotate(angle); >????????? case Square s??? -> s.rotate(angle); >????????? // no default needed! >????? } >? } > > This code, essentially, calls another method based on the type of its parameter > as well as the type of the caller.? This is double-dispatch code, but it's > written with too much ceremony; putting code in blocks of a switch statement > like this encourages users to add complex logic to the switch that should be > factored out: > >? Shape rotate(Shape shape, double angle) { >????? return switch (shape) {?? // pattern matching switch >????????? case Circle c??? -> { >??????????? c.setColor("RED"); >??????????? c.setLine("DASHED"); >??????????? //Some other processing code... >??????????? yield c; >????????? } >????????? case Rectangle r -> { >??????????? r.setColor("RED"); >??????????? r.setLine("DASHED"); >??????????? yield r.rotate(angle); >????????? } >????????? case Square s??? -> { >??????????? s.setColor("RED"); >??????????? s.setLine("DASHED"); >??????????? yield s.rotate(angle); >????????? } >????????? // no default needed! >????? } >? } > > The original intent of inheritance is to bind code like this to the type, rather > than adding potentially risky branching code.? This can get worse if more than > one parameter is passed in: > >? Shape collide(Shape shape1, Shape shape2, double angle) { >????? return switch (shape1) {?? // pattern matching switch >????????? case Circle c??? -> { >??????????? switch (shape2) { >????????????? case Circle c2 -> { >??????????????? //Some pre-processing code... >??????????????? yield findAreaOfCollisionForCircle(c, c2); >????????????? } >????????????? case Rectangle r2 -> { >??????????????? //Some pre-processing code.... >??????????????? yield findAreaOfCollisionForRectangle(c, r2); >????????????? } >????????????? case Square s2 -> { >??????????????? //Some pre-processing code.... >??????????????? yield findAreaOfCollisionForSquare(c, s2); >????????????? } >??????????? } >????????? } >????????? case Rectangle r -> { >??????????? switch (shape2) { >????????????? case Circle c2 -> { >??????????????? //Some pre-processing code... >??????????????? yield findAreaOfCollisionForCircle(r, c2); >????????????? } >????????????? case Rectangle r2 -> { >??????????????? //Some pre-processing code.... >??????????????? yield findAreaOfCollisionForRectangle(r, r2); >????????????? } >????????????? case Square s2 -> { >??????????????? //Some pre-processing code.... >??????????????? yield findAreaOfCollisionForSquare(r, s2); >????????????? } >??????????? } >????????? } >????????? case Square s??? -> { >??????????? //and so on.... >????????? } >????????? // no default needed! >????? } >? } > > Instead, the proposal would be that the compiler generates this code instead, > and allow a multimethod annotation at the interface level: > >? interface Shape { >??? @Multimethod >??? Shape collide(Shape shape, double angle); >? } > >? class Rectangle { >??? @Multimethod >??? Shape collide(Circle shape, double angle) { ... } >??? @Multimethod >??? multi Shape collide(Rectangle shape, double angle) { ... } >??? @Multimethod >??? multi Shape collide(Square square, double angle) { ... } >? } > >? class Rectangle { >??? @Multimethod >??? Shape collide(Circle shape, double angle) { ... } >??? @Multimethod >??? Shape collide(Rectangle shape, double angle) { ... } >??? @Multimethod >??? Shape collide(Square square, double angle) { ... } >? } > >? class Square { >??? @Multimethod >??? Shape collide(Circle shape, double angle) { ... } >??? @Multimethod >??? Shape collide(Rectangle shape, double angle) { ... } >??? @Multimethod >??? Shape collide(Square square, double angle) { ... } >??? // no default needed! >? } > > This reduces the confusion of adding switch statements to auxiliary classes > where class behavior depends on the interaction between two or more types. > ? This would extend to multiple parameters as well: > >? interface Shape { >??? @Multimethod >??? Shape collideTwo(Shape shape1, Shape shape2, double angle); >? } > >? class Circle { >??? @Multimethod >??? Shape collideTwo(Circle shape1, Circle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Circle shape1, Rectangle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Circle shape1, Square shape2, double angle) { ... } > >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Circle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Rectangle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Square shape2, double angle) { ... } >??? // ...and so on >? } > >? class Rectangle { >??? @Multimethod >??? Shape collideTwo(Circle shape1, Circle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Circle shape1, Rectangle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Circle shape1, Square shape2, double angle) { ... } > >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Circle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Rectangle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Square shape2, double angle) { ... } >? } > > Thus, the branching code is reduced, and the functionality is encapsulated into > methods, improving readability and reducing testing complexity.? The compiler > internally can generate the complex switch statements, which is essentially the > equivalent of the above. > Would such a feature be worthy of consideration? > Thanks for your time,Brad Markel Disclaimer, i hold a Phd on multi-emthod implementation in Java. With multi-methods, you have two parts, one is how to compile them safely the other is how to implement them efficiently. For the first part, if we suppose we want to keep the Java semantics when doing a meultimethod call, there is a huge issue, when you have a multu-methods like void foo(Object o, String s) and void foo(String s, Object o) and dynamically both arguments are String, which method you should call ? There are another issue with null but it's manageable. This preclude to introduce multi-methods directly in the language. The second part is far easier, Java 7 introduces invokedynamic and method handles, so we can simulate a muti-dispatch quite easily, as an example, you can use the library Macro [1] for that, by declaring that all parameters are CONSTANT_CLASS.polymorphic(), the actuel code is left as an exercise :) About the relation between switch and multi-methods, a switch on a tuple is a multi-method. Let suppose we have the record record ShapePair(Shape shape1, Shape shape2) { } JEP 405 [2], introduces record pattern (my hope is to have it available for Java 19), with that one can write Shape collide(Shape shape1, Shape shape2, double angle) { return switch (new ShapePair(shape1, shape2)) { case ShapePair(Circle c1, Circle c2) -> { ... } case ShapePair(Rectangle r, Circle c) -> { ... } ... }; } which is i believe what you want. regards, R?mi [1] https://github.com/forax/macro [2] https://openjdk.java.net/jeps/405 From knightblue610 at yahoo.com Fri Jan 21 16:58:24 2022 From: knightblue610 at yahoo.com (Brad Markel) Date: Fri, 21 Jan 2022 16:58:24 +0000 (UTC) Subject: Multimethods feature proposal In-Reply-To: <1008331739.1891139.1642781139513.JavaMail.zimbra@u-pem.fr> References: <1179795615.419772.1642779001692.ref@mail.yahoo.com> <1179795615.419772.1642779001692@mail.yahoo.com> <1008331739.1891139.1642781139513.JavaMail.zimbra@u-pem.fr> Message-ID: <163282461.441500.1642784304774@mail.yahoo.com> >For the first part, if we suppose we want to keep the Java semantics when doing a meultimethod call, there is a >huge issue, >when you have a multu-methods like >? void foo(Object o, String s) and >? void foo(String s, Object o) >and dynamically both arguments are String, which method you should call ? >There are another issue with null but it's manageable. Thank you for your feedback!? I think there may be a way out of the first problem. In the case of sealed classes, there's a finite number of cases, and the proposal would force you to implement all combinations of the parameters thereof.? So, in the case of Shape: ? interface Shape { ??? @Multimethod ??? Shape collideTwo(Shape shape1, Shape shape2, double angle); ? } ? class Rectangle { ????? @Multimethod ????? Shape collideTwo(Circle shape1, Circle shape2, double angle) { ... } ????? @Multimethod ????? Shape collideTwo(Circle shape1, Rectangle shape2, double angle) { ... } ????? @Multimethod ????? Shape collideTwo(Circle shape1, Square shape2, double angle) { ... } ? ????? @Multimethod ????? Shape collideTwo(Rectangle shape1, Circle shape2, double angle) { ... } ????? @Multimethod ????? Shape collideTwo(Rectangle shape1, Rectangle shape2, double angle) { ... } ????? @Multimethod ????? Shape collideTwo(Rectangle shape1, Square shape2, double angle) { ... } ? } If you pass in a Square: ? collideTwo(square, circle, 10.0); ? collideTwo(circle, square, 10.0); If you tried this: ? collideTwo(triangle, circle, 10.0); ? //error - Triangle is not in the permits clause of sealed class Shape And if you tried this: class Rectangle { ??? @Multimethod ??? Shape collideTwo(Circle shape1, Circle shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Circle shape1, Rectangle shape2, double angle) { ... } ?? ? ??? //error - collideTwo(circle, square, angle) is not implemented. ??? @Multimethod ??? Shape collideTwo(Rectangle shape1, Circle shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Rectangle shape1, Rectangle shape2, double angle) { ... } ??? @Multimethod ??? Shape collideTwo(Rectangle shape1, Square shape2, double angle) { ... } } Both cases are implemented, so you can pick the correct one.? We'd have to specify that only sealed classes are eligible as parameters.? The trick here is that the number of combinations is finite. Now, as for nulls, you could specify the null case like you would in the switch: ? class Rectangle { ????? @Multimethod ????? Shape collideTwo(Circle shape1, Circle shape2, double angle) { ... } ????? @Multimethod ????? Shape collideTwo(Circle shape1, Rectangle shape2, double angle) { ... } ???? ? ????? //error - collideTwo(circle, square, angle) is not implemented. ????? @Multimethod ????? Shape collideTwo(Rectangle shape1, Circle shape2, double angle) { ... } ????? @Multimethod ????? Shape collideTwo(Rectangle shape1, Rectangle shape2, double angle) { ... } ????? @Multimethod ????? Shape collideTwo(Rectangle shape1, Square shape2, double angle) { ... } ????? @Multimethod ????? Shape collideTwo(Shape nullPointer, Shape nullPointer, double angle) { ... } ? } Thanks, Brad Markel On Friday, January 21, 2022, 10:05:46 AM CST, Remi Forax wrote: ----- Original Message ----- > From: "Brad Markel" > To: "amber-dev" > Sent: Friday, January 21, 2022 4:30:01 PM > Subject: Multimethods feature proposal > Greetings, > First, let me start off by apologizing if I'm emailing the wrong mailing list. > ? I understand that the JEP process suggests emailing feature requests to > another list, but since this is so closely related to the Amber development of > pattern matching with sealed classes, I felt it wise to run this by the team > before submitting a JEP to that list. > > Consider the use of a sealed type in a switch expression as envisioned by the > current second preview feature in JDK 17: > >? Shape rotate(Shape shape, double angle) { >????? return switch (shape) {?? // pattern matching switch >????????? case Circle c??? -> c; >????????? case Rectangle r -> r.rotate(angle); >????????? case Square s??? -> s.rotate(angle); >????????? // no default needed! >????? } >? } > > This code, essentially, calls another method based on the type of its parameter > as well as the type of the caller.? This is double-dispatch code, but it's > written with too much ceremony; putting code in blocks of a switch statement > like this encourages users to add complex logic to the switch that should be > factored out: > >? Shape rotate(Shape shape, double angle) { >????? return switch (shape) {?? // pattern matching switch >????????? case Circle c??? -> { >??????????? c.setColor("RED"); >??????????? c.setLine("DASHED"); >??????????? //Some other processing code... >??????????? yield c; >????????? } >????????? case Rectangle r -> { >??????????? r.setColor("RED"); >??????????? r.setLine("DASHED"); >??????????? yield r.rotate(angle); >????????? } >????????? case Square s??? -> { >??????????? s.setColor("RED"); >??????????? s.setLine("DASHED"); >??????????? yield s.rotate(angle); >????????? } >????????? // no default needed! >????? } >? } > > The original intent of inheritance is to bind code like this to the type, rather > than adding potentially risky branching code.? This can get worse if more than > one parameter is passed in: > >? Shape collide(Shape shape1, Shape shape2, double angle) { >????? return switch (shape1) {?? // pattern matching switch >????????? case Circle c??? -> { >??????????? switch (shape2) { >????????????? case Circle c2 -> { >??????????????? //Some pre-processing code... >??????????????? yield findAreaOfCollisionForCircle(c, c2); >????????????? } >????????????? case Rectangle r2 -> { >??????????????? //Some pre-processing code.... >??????????????? yield findAreaOfCollisionForRectangle(c, r2); >????????????? } >????????????? case Square s2 -> { >??????????????? //Some pre-processing code.... >??????????????? yield findAreaOfCollisionForSquare(c, s2); >????????????? } >??????????? } >????????? } >????????? case Rectangle r -> { >??????????? switch (shape2) { >????????????? case Circle c2 -> { >??????????????? //Some pre-processing code... >??????????????? yield findAreaOfCollisionForCircle(r, c2); >????????????? } >????????????? case Rectangle r2 -> { >??????????????? //Some pre-processing code.... >??????????????? yield findAreaOfCollisionForRectangle(r, r2); >????????????? } >????????????? case Square s2 -> { >??????????????? //Some pre-processing code.... >??????????????? yield findAreaOfCollisionForSquare(r, s2); >????????????? } >??????????? } >????????? } >????????? case Square s??? -> { >??????????? //and so on.... >????????? } >????????? // no default needed! >????? } >? } > > Instead, the proposal would be that the compiler generates this code instead, > and allow a multimethod annotation at the interface level: > >? interface Shape { >??? @Multimethod >??? Shape collide(Shape shape, double angle); >? } > >? class Rectangle { >??? @Multimethod >??? Shape collide(Circle shape, double angle) { ... } >??? @Multimethod >??? multi Shape collide(Rectangle shape, double angle) { ... } >??? @Multimethod >??? multi Shape collide(Square square, double angle) { ... } >? } > >? class Rectangle { >??? @Multimethod >??? Shape collide(Circle shape, double angle) { ... } >??? @Multimethod >??? Shape collide(Rectangle shape, double angle) { ... } >??? @Multimethod >??? Shape collide(Square square, double angle) { ... } >? } > >? class Square { >??? @Multimethod >??? Shape collide(Circle shape, double angle) { ... } >??? @Multimethod >??? Shape collide(Rectangle shape, double angle) { ... } >??? @Multimethod >??? Shape collide(Square square, double angle) { ... } >??? // no default needed! >? } > > This reduces the confusion of adding switch statements to auxiliary classes > where class behavior depends on the interaction between two or more types. > ? This would extend to multiple parameters as well: > >? interface Shape { >??? @Multimethod >??? Shape collideTwo(Shape shape1, Shape shape2, double angle); >? } > >? class Circle { >??? @Multimethod >??? Shape collideTwo(Circle shape1, Circle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Circle shape1, Rectangle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Circle shape1, Square shape2, double angle) { ... } > >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Circle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Rectangle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Square shape2, double angle) { ... } >??? // ...and so on >? } > >? class Rectangle { >??? @Multimethod >??? Shape collideTwo(Circle shape1, Circle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Circle shape1, Rectangle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Circle shape1, Square shape2, double angle) { ... } > >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Circle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Rectangle shape2, double angle) { ... } >??? @Multimethod >??? Shape collideTwo(Rectangle shape1, Square shape2, double angle) { ... } >? } > > Thus, the branching code is reduced, and the functionality is encapsulated into > methods, improving readability and reducing testing complexity.? The compiler > internally can generate the complex switch statements, which is essentially the > equivalent of the above. > Would such a feature be worthy of consideration? > Thanks for your time,Brad Markel Disclaimer, i hold a Phd on multi-emthod implementation in Java. With multi-methods, you have two parts, one is how to compile them safely the other is how to implement them efficiently. For the first part, if we suppose we want to keep the Java semantics when doing a meultimethod call, there is a huge issue, when you have a multu-methods like ? void foo(Object o, String s) and ? void foo(String s, Object o) and dynamically both arguments are String, which method you should call ? There are another issue with null but it's manageable. This preclude to introduce multi-methods directly in the language. The second part is far easier, Java 7 introduces invokedynamic and method handles, so we can simulate a muti-dispatch quite easily, as an example, you can use the library Macro [1] for that, by declaring that all parameters are CONSTANT_CLASS.polymorphic(), the actuel code is left as an exercise :) About the relation between switch and multi-methods, a switch on a tuple is a multi-method. Let suppose we have the record ? record ShapePair(Shape shape1, Shape shape2) { } JEP 405 [2], introduces record pattern (my hope is to have it available for Java 19), with that one can write ? Shape collide(Shape shape1, Shape shape2, double angle) { ? ? return switch (new ShapePair(shape1, shape2)) { ? ? case ShapePair(Circle c1, Circle c2) -> { ... } ? ? case ShapePair(Rectangle r, Circle c) -> { ... } ? ? ... ? }; ? } which is i believe what you want. ? regards, R?mi [1] https://github.com/forax/macro [2] https://openjdk.java.net/jeps/405 ? From brian.goetz at oracle.com Fri Jan 21 17:12:53 2022 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 21 Jan 2022 12:12:53 -0500 Subject: Multimethods feature proposal In-Reply-To: <1179795615.419772.1642779001692@mail.yahoo.com> References: <1179795615.419772.1642779001692.ref@mail.yahoo.com> <1179795615.419772.1642779001692@mail.yahoo.com> Message-ID: <31f89188-09af-798a-06d1-2b7c232328ac@oracle.com> In theory, there's nothing wrong with multiple dispatch.? But I think it's a poor candidate for layering atop of Java today; having two kinds of instance dispatch, which depends on how the invoked method was declared, adds a lot of complexity not only to the language, but also asks users to reason about something complex and new in addition to the dispatch rules they've already internalized (and may move some type checking from compile time to run time.)? We'd likely also have to have different rules for allowable overloads of multi methods than we do for single-dispatch methods (e.g., maybe we need a "meet" rule).? If multiple dispatch were the *only* form of dispatch, this would be different, but we picked our dispatch-horse 25 years ago.? So, the cost is large, both for the language, and for the users. So, what's the payback?? I think its more limited than it might first appear. It is a not-too-uncommon reaction, when someone first sees pattern matching, to say "what do we need that for, we have virtual methods."? What this misses is that some operations are indeed important enough to put in our object hierarchy, but some operations are purely ad-hoc polymorphism.? And there's a range in between. Multi-method dispatch addresses a part of the intermediate range between "just put it in the hierarchy" and "completely ad-hoc", but only a part.? So all this complexity is in aid of improving only a small slice of the things we might express by pattern switches. I also don't think most Java developers will think programming in the model you propose seems "better"; I think they'll see it as just more ceremony, and so are more likely to write the "duplicative" code you are concerned about anyway. On 1/21/2022 10:30 AM, Brad Markel wrote: > Greetings, > First, let me start off by apologizing if I'm emailing the wrong mailing list.? I understand that the JEP process suggests emailing feature requests to another list, but since this is so closely related to the Amber development of pattern matching with sealed classes, I felt it wise to run this by the team before submitting a JEP to that list. > > Consider the use of a sealed type in a switch expression as envisioned by the current second preview feature in JDK 17: > > ? Shape rotate(Shape shape, double angle) { > ????? return switch (shape) {?? // pattern matching switch > ????????? case Circle c??? -> c; > ????????? case Rectangle r -> r.rotate(angle); > ????????? case Square s??? -> s.rotate(angle); > ????????? // no default needed! > ????? } > ? } > > This code, essentially, calls another method based on the type of its parameter as well as the type of the caller.? This is double-dispatch code, but it's written with too much ceremony; putting code in blocks of a switch statement like this encourages users to add complex logic to the switch that should be factored out: > > ? Shape rotate(Shape shape, double angle) { > ????? return switch (shape) {?? // pattern matching switch > ????????? case Circle c??? -> { > ??????????? c.setColor("RED"); > ??????????? c.setLine("DASHED"); > ??????????? //Some other processing code... > ??????????? yield c; > ????????? } > ????????? case Rectangle r -> { > ??????????? r.setColor("RED"); > ??????????? r.setLine("DASHED"); > ??????????? yield r.rotate(angle); > ????????? } > ????????? case Square s??? -> { > ??????????? s.setColor("RED"); > ??????????? s.setLine("DASHED"); > ??????????? yield s.rotate(angle); > ????????? } > ????????? // no default needed! > ????? } > ? } > > The original intent of inheritance is to bind code like this to the type, rather than adding potentially risky branching code.? This can get worse if more than one parameter is passed in: > > ? Shape collide(Shape shape1, Shape shape2, double angle) { > ????? return switch (shape1) {?? // pattern matching switch > ????????? case Circle c??? -> { > ??????????? switch (shape2) { > ????????????? case Circle c2 -> { > ??????????????? //Some pre-processing code... > ??????????????? yield findAreaOfCollisionForCircle(c, c2); > ????????????? } > ????????????? case Rectangle r2 -> { > ??????????????? //Some pre-processing code.... > ??????????????? yield findAreaOfCollisionForRectangle(c, r2); > ????????????? } > ????????????? case Square s2 -> { > ??????????????? //Some pre-processing code.... > ??????????????? yield findAreaOfCollisionForSquare(c, s2); > ????????????? } > ??????????? } > ????????? } > ????????? case Rectangle r -> { > ??????????? switch (shape2) { > ????????????? case Circle c2 -> { > ??????????????? //Some pre-processing code... > ??????????????? yield findAreaOfCollisionForCircle(r, c2); > ????????????? } > ????????????? case Rectangle r2 -> { > ??????????????? //Some pre-processing code.... > ??????????????? yield findAreaOfCollisionForRectangle(r, r2); > ????????????? } > ????????????? case Square s2 -> { > ??????????????? //Some pre-processing code.... > ??????????????? yield findAreaOfCollisionForSquare(r, s2); > ????????????? } > ??????????? } > ????????? } > ????????? case Square s??? -> { > ??????????? //and so on.... > ????????? } > ????????? // no default needed! > ????? } > ? } > > Instead, the proposal would be that the compiler generates this code instead, and allow a multimethod annotation at the interface level: > > ? interface Shape { > ??? @Multimethod > ??? Shape collide(Shape shape, double angle); > ? } > > ? class Rectangle { > ??? @Multimethod > ??? Shape collide(Circle shape, double angle) { ... } > ??? @Multimethod > ??? multi Shape collide(Rectangle shape, double angle) { ... } > ??? @Multimethod > ??? multi Shape collide(Square square, double angle) { ... } > ? } > > ? class Rectangle { > ??? @Multimethod > ??? Shape collide(Circle shape, double angle) { ... } > ??? @Multimethod > ??? Shape collide(Rectangle shape, double angle) { ... } > ??? @Multimethod > ??? Shape collide(Square square, double angle) { ... } > ? } > > ? class Square { > ??? @Multimethod > ??? Shape collide(Circle shape, double angle) { ... } > ??? @Multimethod > ??? Shape collide(Rectangle shape, double angle) { ... } > ??? @Multimethod > ??? Shape collide(Square square, double angle) { ... } > ??? // no default needed! > ? } > > This reduces the confusion of adding switch statements to auxiliary classes where class behavior depends on the interaction between two or more types.? This would extend to multiple parameters as well: > > ? interface Shape { > ??? @Multimethod > ??? Shape collideTwo(Shape shape1, Shape shape2, double angle); > ? } > > ? class Circle { > ??? @Multimethod > ??? Shape collideTwo(Circle shape1, Circle shape2, double angle) { ... } > ??? @Multimethod > ??? Shape collideTwo(Circle shape1, Rectangle shape2, double angle) { ... } > ??? @Multimethod > ??? Shape collideTwo(Circle shape1, Square shape2, double angle) { ... } > > ??? @Multimethod > ??? Shape collideTwo(Rectangle shape1, Circle shape2, double angle) { ... } > ??? @Multimethod > ??? Shape collideTwo(Rectangle shape1, Rectangle shape2, double angle) { ... } > ??? @Multimethod > ??? Shape collideTwo(Rectangle shape1, Square shape2, double angle) { ... } > ??? // ...and so on > ? } > > ? class Rectangle { > ??? @Multimethod > ??? Shape collideTwo(Circle shape1, Circle shape2, double angle) { ... } > ??? @Multimethod > ??? Shape collideTwo(Circle shape1, Rectangle shape2, double angle) { ... } > ??? @Multimethod > ??? Shape collideTwo(Circle shape1, Square shape2, double angle) { ... } > > ??? @Multimethod > ??? Shape collideTwo(Rectangle shape1, Circle shape2, double angle) { ... } > ??? @Multimethod > ??? Shape collideTwo(Rectangle shape1, Rectangle shape2, double angle) { ... } > ??? @Multimethod > ??? Shape collideTwo(Rectangle shape1, Square shape2, double angle) { ... } > ? } > > Thus, the branching code is reduced, and the functionality is encapsulated into methods, improving readability and reducing testing complexity.? The compiler internally can generate the complex switch statements, which is essentially the equivalent of the above. > Would such a feature be worthy of consideration? > Thanks for your time,Brad Markel > >