From forax at univ-mlv.fr Sat Dec 1 12:46:46 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sat, 1 Dec 2018 13:46:46 +0100 (CET) Subject: Sealed types In-Reply-To: References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <1747463018.1229943.1543564739664.JavaMail.zimbra@u-pem.fr> Message-ID: <1466003307.1453690.1543668406907.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Vendredi 30 Novembre 2018 16:30:45 > Objet: Re: Sealed types >>> sealed interface Node { ... } >>> In this streamlined form, Node may be extended only by its nestmates. This may >>> be suitable for many situations, but not for all; in this case, the user may >>> specify an explicit permits list: >>> sealed interface Node >>> permits FooNode, BarNode { ... } >>> The two forms may not be combined; if there is a permits list, it must list all >>> the permitted subtypes. We can think of the simple form as merely inferring the >>> permits clause from information in the same compilation unit. >> what inferring from the same compilation unit means ? > It means: if there is no "permits" list, the compiler is allowed to synthesize > one that contains exactly the list of subtypes declared in the same compilation > unit. (Much like the way we synthesize the NestMembers attribute.) >> - it works for anonymous class declared in the same compilation unit ? > Anonymous classes are a good question. On the one hand, I can imagine when they > might be useful. On the other, it means there's no way anyone -- including the > current class, to which all the subtypes are accessible -- will be able to > switch exhaustively over them. I think we should consider banning them, as you > can always fall back to a named class. The exhaustiveness part is important, > and too easy to forget about when you're writing the class. Exhaustiveness is one consequence of a sealed interface, but having a sealed interface, i.e. constraining all subtypes to be defined in the same compilation unit is useful even without exhaustiveness. It's a way to allow to make an interface visible without having to care about implementations you do not know. In a previous mail, you said that if an implementation class is not visible from a switch, then the compiler will just ask for a default. An anonymous class (or a local class of a method for completeness) is just a class which is not visible from any switches. > (Other restrictions: classes in the permits list must be in the same module (or > if not in a module, same package and protection domain) as the sealed type, and > must be accessible to the sealed type.) For me, a sealed type and all its implementation are nestmates from the VM point of view (it's stronger that you are proposing), they are part of the same compilation unit. If you have a hierarchy of sealed interfaces in the same compilation unit sealed interface Foo { } record FooImpl implements Foo { } sealed interface Bar extends Foo { } record BarImpl implements Bar {} the compiler generates that - Foo permits FooImpl and Bar - Bar permits BarImpl - Foo, FooImpl, Bar and BarImpl are nestmates >> - it works for functional interface for lambda in the same compilation unit ? > Same as for anon classes; if we ban one, we ban the other. >> what exactly the permit list contains ?? >> (dynamic nestmate/sealed class to the rescue ?) > I don't seen an interaction with dynamic nestmates. The permits list is an > explicit list; if the dynamic class is not on the list, it can't be a subtype, > even if its in the nest. If we infer the list, we're not recording "all > nestmates", but instead inferring it to be the list of nestmates known at > compile time. see above, if we allow anonymous class, we have to allow lambdas (we already support refactoring from one to the other). Given that the class of a lambda is only knows at runtime, it means that the nestmate attributes and the sealed attributes have to be updated at runtime. >> And in another compilation unit >> Fun fun = x -> 2 * x; // rejected because Fun is sealed ? > Yep. >>> Note: It might be allowable for VM support to follow in a later version, rather >>> than delaying the feature entirely. >> Does seems to be a good idea to divorce the two. It means you can create classes >> with ASM that will work for a version of the VM but not the next one. > I think you mean "does not seem to be a good idea"? I agree, it's got problems, > such as the ones you raise. But it is a possible move we can make, so we can > keep it on the board until we have more visibility into the cost of waiting for > VM support. Ok ! >>> Accessibility. Subtypes need not be as accessible as the sealed parent. In this >>> case, clients are not going to get the chance to exhaustively switch over them; >>> they?ll have to make these switches exhaustive with a default clause or other >>> total pattern. When compiling a switch over such a sealed type, the compiler >>> can provide a useful error message (?I know this is a sealed type, but I can?t >>> provide full exhaustiveness checking here because you can?t see all the >>> subtypes, so you still need a default.?) >> Yes ! >> I expect a public sealed interface with several package private implementations >> to be a common pattern. > I agree qualitatively (and share your conclusion), but disagree with the > quantitative statement here ("common"). I think it will be common _for platform > and low-level library implementors_ to do this. But if the feature is > successful, this will probably be a tiny fraction of the sealed types in the > world. So yes, this is an important pattern, but I hope it is UNcommon. yes, it will be common for library implementors >> The implication is that you can not define (at least now) the implementations >> inside the sealed interface because all class members of an interface can only >> be public so you can not declare a package-private implementation. I don't know >> exactly why private class are not allowed in interface, it seems to be an >> overlook for me. > Yes, this (and fields too, even though they kind of suck in interfaces), was > largely overlooked. We are gathering a list of "gratuitous nesting > constraints", of which this is one, and at some point will bang them all out. > (Restrictions on static members in nested classes is another. Local interfaces > and enums is another. Maybe even local methods. Etc.) yes, not having local interface is weird too. I'm in for local methods iff we come with a syntax that is not too close to a local variable declaration; R?mi From brian.goetz at oracle.com Sat Dec 1 13:26:03 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 1 Dec 2018 08:26:03 -0500 Subject: Sealed types In-Reply-To: <1466003307.1453690.1543668406907.JavaMail.zimbra@u-pem.fr> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <1747463018.1229943.1543564739664.JavaMail.zimbra@u-pem.fr> <1466003307.1453690.1543668406907.JavaMail.zimbra@u-pem.fr> Message-ID: <6035fd14-423c-ea88-0757-58f21e46ad07@oracle.com> > - it works for anonymous class declared in the same > compilation unit ? > > > Anonymous classes are a good question.? On the one hand, I can > imagine when they might be useful.? On the other, it means there's > no way anyone -- including the current class, to which all the > subtypes are accessible -- will be able to switch exhaustively > over them.? I think we should consider banning them, as you can > always fall back to a named class. The exhaustiveness part is > important, and too easy to forget about when you're writing the > class. > > > Exhaustiveness is one consequence of a sealed interface, but having a > sealed interface, i.e. constraining all subtypes to be defined in the > same compilation unit is useful even without exhaustiveness. > It's a way to allow to make an interface visible without having to > care about implementations you do not know. > > In a previous mail, you said that if an implementation class is not > visible from a switch, then the compiler will just ask for a default. > An anonymous class (or a local class of a method for completeness) is > just a class which is not visible from any switches. This is all obviously true, but also a kind of false equivalency. Yes, constraint can useful without exhaustiveness.? But, if you make it too easy to undermine exhaustiveness, people will do this without realizing it, and likely for little benefit.? It is really really common that when declaring APIs, users forget about what problems they are creating for their callers.? Examples include: ?- "Why can't I declare defaults for Object methods" (https://stackoverflow.com/questions/24016962/java8-why-is-it-forbidden-to-define-a-default-method-for-a-method-from-java-lan/24026292#24026292) -- because you'd break subclasses. ?- "Why can't default methods be synchronized" (https://stackoverflow.com/questions/23453568/what-is-the-reason-why-synchronized-is-not-allowed-in-java-8-interface-methods?noredirect=1&lq=1) -- because it doesn't mean what you think it means. ?- Use-site ambiguity overload errors resulting from overloads like m(Function) and m(Predicate) -- because then you create an API that no client can use it with lambdas, oops. In each of these cases, the problem is a failure to consider what the impact of the declaration-site decisions are on callers.? So, I think the question of anonymous classes is more subtle than you're giving it credit for. So yes, accessibility can undermine exhaustiveness -- outside the capsule.? (Inside the capsule, you still get exhaustiveness.)? But I see no reason why we should leap from "yes, sometimes it fails" to "so let's make it really easy to make it fail all the time." > > For me, a sealed type and all its implementation are nestmates from > the VM point of view (it's stronger that you are proposing), they are > part of the same compilation unit. Then we cannot allow subtypes outside the nest, because that would be in conflict with the nestmate design.? That's a possible point in the design space, but I think its overly restrictive, and eliminates some really important use cases.? Plus, only .001% of Java developers even know what a nest is, so it seems a little questionable to distort a programming model feature to match a VM feature that no one is aware of. > > see above, if we allow anonymous class, we have to allow lambdas (we > already support refactoring from one to the other). > Given that the class of a lambda is only knows at runtime, it means > that the nestmate attributes and the sealed attributes have to be > updated at runtime. A further argument that trying to say "sealing == nestmates" is pushing on the wrong end of the lever. From forax at univ-mlv.fr Sat Dec 1 16:48:54 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sat, 1 Dec 2018 17:48:54 +0100 (CET) Subject: Sealed types In-Reply-To: <6035fd14-423c-ea88-0757-58f21e46ad07@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <1747463018.1229943.1543564739664.JavaMail.zimbra@u-pem.fr> <1466003307.1453690.1543668406907.JavaMail.zimbra@u-pem.fr> <6035fd14-423c-ea88-0757-58f21e46ad07@oracle.com> Message-ID: <1842539190.1463616.1543682934822.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Samedi 1 D?cembre 2018 14:26:03 > Objet: Re: Sealed types >>>> - it works for anonymous class declared in the same compilation unit ? >>> Anonymous classes are a good question. On the one hand, I can imagine when they >>> might be useful. On the other, it means there's no way anyone -- including the >>> current class, to which all the subtypes are accessible -- will be able to >>> switch exhaustively over them. I think we should consider banning them, as you >>> can always fall back to a named class. The exhaustiveness part is important, >>> and too easy to forget about when you're writing the class. >> Exhaustiveness is one consequence of a sealed interface, but having a sealed >> interface, i.e. constraining all subtypes to be defined in the same compilation >> unit is useful even without exhaustiveness. >> It's a way to allow to make an interface visible without having to care about >> implementations you do not know. >> In a previous mail, you said that if an implementation class is not visible from >> a switch, then the compiler will just ask for a default. >> An anonymous class (or a local class of a method for completeness) is just a >> class which is not visible from any switches. > This is all obviously true, but also a kind of false equivalency. Yes, > constraint can useful without exhaustiveness. But, if you make it too easy to > undermine exhaustiveness, people will do this without realizing it, and likely > for little benefit. It is really really common that when declaring APIs, users > forget about what problems they are creating for their callers. Exhaustiveness requires names (and visibility) but forcing names and we are back in the kingdom of nouns. I understand you're worry that a small patch to a compilation unit may destroy the exhaustiveness without developer seeing that, but in my opinion we should not choose to not allow anonymous classes but instead make the contract more explicit exactly like @FunctionalInterface make the contract explicit. So we should add an annotation @Exhaustiveness that ask the compiler to verify that all the implementations are named and as visible as the interface. > Examples include: > - "Why can't I declare defaults for Object methods" ( [ > https://stackoverflow.com/questions/24016962/java8-why-is-it-forbidden-to-define-a-default-method-for-a-method-from-java-lan/24026292#24026292 > | > https://stackoverflow.com/questions/24016962/java8-why-is-it-forbidden-to-define-a-default-method-for-a-method-from-java-lan/24026292#24026292 > ] ) -- because you'd break subclasses. > - "Why can't default methods be synchronized" ( [ > https://stackoverflow.com/questions/23453568/what-is-the-reason-why-synchronized-is-not-allowed-in-java-8-interface-methods?noredirect=1&lq=1 > | > https://stackoverflow.com/questions/23453568/what-is-the-reason-why-synchronized-is-not-allowed-in-java-8-interface-methods?noredirect=1&lq=1 > ] ) -- because it doesn't mean what you think it means. > - Use-site ambiguity overload errors resulting from overloads like m(Function) > and m(Predicate) -- because then you create an API that no client can use it > with lambdas, oops. > In each of these cases, the problem is a failure to consider what the impact of > the declaration-site decisions are on callers. So, I think the question of > anonymous classes is more subtle than you're giving it credit for. > So yes, accessibility can undermine exhaustiveness -- outside the capsule. > (Inside the capsule, you still get exhaustiveness.) But I see no reason why we > should leap from "yes, sometimes it fails" to "so let's make it really easy to > make it fail all the time." Not wanting exhaustiveness is not a failure. The annotation IWantExhaustivness or IDontWantExhaustivness solve that. >> For me, a sealed type and all its implementation are nestmates from the VM point >> of view (it's stronger that you are proposing), they are part of the same >> compilation unit. > Then we cannot allow subtypes outside the nest, because that would be in > conflict with the nestmate design. That's a possible point in the design space, > but I think its overly restrictive, and eliminates some really important use > cases. Plus, only .001% of Java developers even know what a nest is, so it > seems a little questionable to distort a programming model feature to match a > VM feature that no one is aware of. Nestmate is currently used as a mechanism to heal the divorce between a class for the JLS and a class for the JVMS but it can be used to represent a compilation unit too. The programming model is that a sealed interface and all its implementations has to be in the same compilation unit (it's the premise of this discussion, or did i overlook an email ?). >> see above, if we allow anonymous class, we have to allow lambdas (we already >> support refactoring from one to the other). >> Given that the class of a lambda is only knows at runtime, it means that the >> nestmate attributes and the sealed attributes have to be updated at runtime. > A further argument that trying to say "sealing == nestmates" is pushing on the > wrong end of the lever. sealing is not nestmates, sealing has IMO two parts, one is that a a sealed interface should list all its implementation the other is the access rules between a sealed interface and its implementation. Netsmates can be used to represent the latter. You still need a Sealed attribute for the former. R?mi From forax at univ-mlv.fr Sat Dec 1 17:58:04 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sat, 1 Dec 2018 18:58:04 +0100 (CET) Subject: Sealed types In-Reply-To: <6035fd14-423c-ea88-0757-58f21e46ad07@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <1747463018.1229943.1543564739664.JavaMail.zimbra@u-pem.fr> <1466003307.1453690.1543668406907.JavaMail.zimbra@u-pem.fr> <6035fd14-423c-ea88-0757-58f21e46ad07@oracle.com> Message-ID: <558143021.5250.1543687084973.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" < brian.goetz at oracle.com > > ?: "Remi Forax" < forax at univ-mlv.fr > > Cc: "amber-spec-experts" < amber-spec-experts at openjdk.java.net > > Envoy?: Samedi 1 D?cembre 2018 14:26:03 > Objet: Re: Sealed types >>>> - it works for anonymous class declared in the same compilation unit ? >>> Anonymous classes are a good question. On the one hand, I can imagine when they >>> might be useful. On the other, it means there's no way anyone -- including the >>> current class, to which all the subtypes are accessible -- will be able to >>> switch exhaustively over them. I think we should consider banning them, as you >>> can always fall back to a named class. The exhaustiveness part is important, >>> and too easy to forget about when you're writing the class. >> Exhaustiveness is one consequence of a sealed interface, but having a sealed >> interface, i.e. constraining all subtypes to be defined in the same compilation >> unit is useful even without exhaustiveness. >> It's a way to allow to make an interface visible without having to care about >> implementations you do not know. >> In a previous mail, you said that if an implementation class is not visible from >> a switch, then the compiler will just ask for a default. >> An anonymous class (or a local class of a method for completeness) is just a >> class which is not visible from any switches. > This is all obviously true, but also a kind of false equivalency. Yes, > constraint can useful without exhaustiveness. But, if you make it too easy to > undermine exhaustiveness, people will do this without realizing it, and likely > for little benefit. It is really really common that when declaring APIs, users > forget about what problems they are creating for their callers. Exhaustiveness requires names (and visibility) but forcing names and we are back in the kingdom of nouns. I understand you're worry that a small patch to a compilation unit may destroy the exhaustiveness without developer seeing that, but in my opinion we should not choose to not allow anonymous classes but instead make the contract more explicit exactly like @FunctionalInterface make the contract explicit. So we should add an annotation @Exhaustiveness that ask the compiler to verify that all the implementations are named and as visible as the interface. > Examples include: > - "Why can't I declare defaults for Object methods" ( [ > https://stackoverflow.com/questions/24016962/java8-why-is-it-forbidden-to-define-a-default-method-for-a-method-from-java-lan/24026292#24026292 > | > https://stackoverflow.com/questions/24016962/java8-why-is-it-forbidden-to-define-a-default-method-for-a-method-from-java-lan/24026292#24026292 > ] ) -- because you'd break subclasses. > - "Why can't default methods be synchronized" ( [ > https://stackoverflow.com/questions/23453568/what-is-the-reason-why-synchronized-is-not-allowed-in-java-8-interface-methods?noredirect=1&lq=1 > | > https://stackoverflow.com/questions/23453568/what-is-the-reason-why-synchronized-is-not-allowed-in-java-8-interface-methods?noredirect=1&lq=1 > ] ) -- because it doesn't mean what you think it means. > - Use-site ambiguity overload errors resulting from overloads like m(Function) > and m(Predicate) -- because then you create an API that no client can use it > with lambdas, oops. > In each of these cases, the problem is a failure to consider what the impact of > the declaration-site decisions are on callers. So, I think the question of > anonymous classes is more subtle than you're giving it credit for. > So yes, accessibility can undermine exhaustiveness -- outside the capsule. > (Inside the capsule, you still get exhaustiveness.) But I see no reason why we > should leap from "yes, sometimes it fails" to "so let's make it really easy to > make it fail all the time." Not wanting exhaustiveness is not a failure. The annotation IWantExhaustivness or IDontWantExhaustivness solve that. >> For me, a sealed type and all its implementation are nestmates from the VM point >> of view (it's stronger that you are proposing), they are part of the same >> compilation unit. > Then we cannot allow subtypes outside the nest, because that would be in > conflict with the nestmate design. That's a possible point in the design space, > but I think its overly restrictive, and eliminates some really important use > cases. Plus, only .001% of Java developers even know what a nest is, so it > seems a little questionable to distort a programming model feature to match a > VM feature that no one is aware of. Nestmate is currently used as a mechanism to heal the divorce between a class for the JLS and a class for the JVMS but it can be used to represent a compilation unit too. The programming model is that a sealed interface and all its implementations has to be in the same compilation unit (it's the premise of this discussion, or did i overlook an email ?). >> see above, if we allow anonymous class, we have to allow lambdas (we already >> support refactoring from one to the other). >> Given that the class of a lambda is only knows at runtime, it means that the >> nestmate attributes and the sealed attributes have to be updated at runtime. > A further argument that trying to say "sealing == nestmates" is pushing on the > wrong end of the lever. sealing is not nestmates, sealing has IMO two parts, one is that a a sealed interface should list all its implementation the other is the access rules between a sealed interface and its implementation. Netsmates can be used to represent the latter. You still need a Sealed attribute for the former. R?mi From maurizio.cimadamore at oracle.com Wed Dec 5 16:14:59 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Wed, 5 Dec 2018 16:14:59 +0000 Subject: enhanced enums - back from the dead? Message-ID: Hi, as mentioned in [1], the work on enhanced enum stopped while ago as we have found some interoperability issues between generic enums and standard enum APIs such as EnumSet/EnumMap. Recently, we have discussed a possible approach that might get us out of the woods, which is described in greater details here: http://cr.openjdk.java.net/~mcimadamore/amber/enhanced-enums.html We have done some internal testing to convince ourselves that, from an operational perspective, where we end up is indeed good. Some external validation might also be very helpful, which is why we're also in the process of releasing the internal patch we have tested internally in the 'enhanced-enums' amber branch (we'll need to polish it a little :-)). Assuming that, usability-wise, our story ticks all the boxes, I think it might be worth discussing a few points: * Do we still like the features described in JEP 301, from an expressiveness point of view? * Both features described in JEP 301 require some sort of massaging. On the one hand sharper typing of enum constants has to take care of binary compatibility of enum constant subclasses into account (for this reason we redefine accessibility of said subclasses along with their binary names). On the other hand, with the newly proposed approach, generic enums also need some language aid (treatment of raw enum constants supertypes). Do we feel that the steps needed in order to accommodate these sharp edges are worth the increase in expressive power delivered by JEP 301? * Our proposed treatment for generic enums raises an additional, more philosophical, question: what are raw types *for* and how happy are we in seeing more of them (in the form of raw enum types)? Cheers Maurizio [1] - http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html From kevinb at google.com Wed Dec 5 19:26:44 2018 From: kevinb at google.com (Kevin Bourrillion) Date: Wed, 5 Dec 2018 11:26:44 -0800 Subject: enhanced enums - back from the dead? In-Reply-To: References: Message-ID: On Wed, Dec 5, 2018 at 8:19 AM Maurizio Cimadamore < maurizio.cimadamore at oracle.com> wrote: * Our proposed treatment for generic enums raises an additional, more > philosophical, question: what are raw types *for* and how happy are we > in seeing more of them (in the form of raw enum types)? > I have looked high and low for valid use cases for raw types (apart from interacting with legacy code), and only ever found... two. 1. As the stepping stone to make a forbidden cast: @SuppressWarnings("unchecked") // safe because runtime type is functionally covariant Iterator objects = (Iterator) strings; A common use case for this is to interact with APIs that forgot their wlidcards, but there are plenty of other cases that can't be "fixed". 2. As the argument to instanceof. Everyone does it, of course, because writing out `instanceof Map` serves no purpose. Thankfully, they're both harmless... and I believe (?) they were even made exempt from "rawtypes" warnings. (If not, I'd opine that they should be.) -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com From kevinb at google.com Wed Dec 5 19:51:18 2018 From: kevinb at google.com (Kevin Bourrillion) Date: Wed, 5 Dec 2018 11:51:18 -0800 Subject: enhanced enums - back from the dead? In-Reply-To: References: Message-ID: Liam reminded me, 3. the upper bound for class literals, which maybe went without saying, but there it is. Also harmless if I am correct that doing anything with the result of `newInstance` etc. always results in one kind of warning or another. Sorry, this is pointless to the real question you're asking, I just hate saying something wrong without correcting it. :-) On Wed, Dec 5, 2018 at 11:26 AM Kevin Bourrillion wrote: > On Wed, Dec 5, 2018 at 8:19 AM Maurizio Cimadamore < > maurizio.cimadamore at oracle.com> wrote: > > * Our proposed treatment for generic enums raises an additional, more >> philosophical, question: what are raw types *for* and how happy are we >> in seeing more of them (in the form of raw enum types)? >> > > I have looked high and low for valid use cases for raw types (apart from > interacting with legacy code), and only ever found... two. > > 1. As the stepping stone to make a forbidden cast: > > @SuppressWarnings("unchecked") // safe because runtime type is > functionally covariant > Iterator objects = (Iterator) strings; > > > A common use case for this is to interact with APIs that forgot their > wlidcards, but there are plenty of other cases that can't be "fixed". > > 2. As the argument to instanceof. Everyone does it, of course, because > writing out `instanceof Map` serves no purpose. > > Thankfully, they're both harmless... and I believe (?) they were even made > exempt from "rawtypes" warnings. (If not, I'd opine that they should be.) > > > -- > Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com From maurizio.cimadamore at oracle.com Wed Dec 5 21:26:08 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Wed, 5 Dec 2018 21:26:08 +0000 Subject: enhanced enums - back from the dead? In-Reply-To: References: Message-ID: Yes, the compiler special cases raw types warnings for j.l.Class type arguments and also when doing instanceof. That is, in this program: import java.util.List; class Test { ?? void test(List ls) { ???? ls = (List)null; ???? boolean b = (ls instanceof List); ???? Class cl = List.class; ?? } } There will be no raw warnings - just an unchecked warning in the fist assignment. The proposed plan for enums is to allow another 'special case' for raw enum types. Maurizio But a raw warning will be generated for your Iterator example. On 05/12/2018 19:51, Kevin Bourrillion wrote: > Liam reminded me, > > 3. the upper bound for class literals, which maybe went without > saying, but there it is. Also harmless if I am correct that doing > anything with the result of `newInstance` etc. always results in one > kind of warning or another. > > Sorry, this is pointless to the real question you're asking, I just > hate saying something wrong without correcting it. :-) > > > On Wed, Dec 5, 2018 at 11:26 AM Kevin Bourrillion > wrote: > > On Wed, Dec 5, 2018 at 8:19 AM Maurizio Cimadamore > > wrote: > > * Our proposed treatment for generic enums raises an > additional, more > philosophical, question: what are raw types *for* and how > happy are we > in seeing more of them (in the form of raw enum types)? > > > I have looked high and low for valid use cases for raw types > (apart from interacting with legacy code), and only ever found... two. > > 1. As the stepping stone to make a forbidden cast: > > @SuppressWarnings("unchecked") // safe because runtime type is > functionally covariant > Iterator objects = (Iterator) strings; > > > A common use case for this is to interact with APIs that forgot > their wlidcards, but there are plenty of other cases that can't be > "fixed". > > 2. As the argument to instanceof. Everyone does it, of course, > because writing out `instanceof Map` serves no purpose. > > Thankfully, they're both harmless... and I believe (?) they were > even made exempt from "rawtypes" warnings. (If not, I'd opine that > they should be.) > > > -- > Kevin Bourrillion?|?Java Librarian |?Google, > Inc.?|kevinb at google.com > > > > -- > Kevin Bourrillion?|?Java Librarian |?Google, Inc.?|kevinb at google.com > From forax at univ-mlv.fr Wed Dec 5 21:43:16 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 5 Dec 2018 22:43:16 +0100 (CET) Subject: enhanced enums - back from the dead? In-Reply-To: References: Message-ID: <1970970547.807832.1544046196415.JavaMail.zimbra@u-pem.fr> Hi Maurizio, i think you have overlook the fact that raw types and inference also doesn't play well together. accessibility: Widening the type is usually a big No because of the security implication. The fact that the same code code has no security bug with version n but a security hole with version n + 1 scares me. source compatibility: It's may not be a big issue because the JDK source doesn't use 'var'. If a code uses 'var' the sharp type will propagate more, so the JDK is not perhaps the best code to test. friend or foe: the rules for raw types are brutal as you said, but it's by design, it offers maximum compatibility and doesn't allow to mix raw and generic type easily so my students detect the missing angle brackets easily (IntelliJ still doesn't warn about missing angle brackets by default :( ) Now about your example, instead of being functional and wanted each Option to type their argument, you can use ugly side effects instead. So the idea is to use a temporary class instead of a Map to store the data associated with an option. So an Option is something that takes a chunk of the command line arguments and do a side effect on the field of an instance of that temporary class. public class LineParsing { private final HashMap>> actionMap = new HashMap<>(); public LineParsing with(String option, Consumer> action) { actionMap.put(option, action); return this; } public void parse(List args) { var it = args.iterator(); while(it.hasNext()) { actionMap.get(it.next()).accept(it); } } public static void main(String[] args) { var bean = new Object() { Path input = Path.of("input.txt"); boolean all = false; }; new LineParsing() .with("-input", it -> bean.input = Path.of(it.next())) .with("-all", it -> bean.all = true) .parse(List.of(args)); } } regards, R?mi ----- Mail original ----- > De: "Maurizio Cimadamore" > ?: "amber-spec-experts" > Envoy?: Mercredi 5 D?cembre 2018 17:14:59 > Objet: enhanced enums - back from the dead? > Hi, > as mentioned in [1], the work on enhanced enum stopped while ago as we > have found some interoperability issues between generic enums and > standard enum APIs such as EnumSet/EnumMap. > > Recently, we have discussed a possible approach that might get us out of > the woods, which is described in greater details here: > > http://cr.openjdk.java.net/~mcimadamore/amber/enhanced-enums.html > > We have done some internal testing to convince ourselves that, from an > operational perspective, where we end up is indeed good. Some external > validation might also be very helpful, which is why we're also in the > process of releasing the internal patch we have tested internally in the > 'enhanced-enums' amber branch (we'll need to polish it a little :-)). > > Assuming that, usability-wise, our story ticks all the boxes, I think it > might be worth discussing a few points: > > * Do we still like the features described in JEP 301, from an > expressiveness point of view? > > * Both features described in JEP 301 require some sort of massaging. On > the one hand sharper typing of enum constants has to take care of binary > compatibility of enum constant subclasses into account (for this reason > we redefine accessibility of said subclasses along with their binary > names). On the other hand, with the newly proposed approach, generic > enums also need some language aid (treatment of raw enum constants > supertypes). Do we feel that the steps needed in order to accommodate > these sharp edges are worth the increase in expressive power delivered > by JEP 301? > > * Our proposed treatment for generic enums raises an additional, more > philosophical, question: what are raw types *for* and how happy are we > in seeing more of them (in the form of raw enum types)? > > Cheers > Maurizio > > [1] - > http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html From maurizio.cimadamore at oracle.com Thu Dec 6 08:54:09 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Thu, 6 Dec 2018 08:54:09 +0000 Subject: enhanced enums - back from the dead? In-Reply-To: <1970970547.807832.1544046196415.JavaMail.zimbra@u-pem.fr> References: <1970970547.807832.1544046196415.JavaMail.zimbra@u-pem.fr> Message-ID: <4474050b-cfca-d40b-3712-26f8109a8456@oracle.com> Hi Remi, some comments inline. On 05/12/2018 21:43, Remi Forax wrote: > Hi Maurizio, > i think you have overlook the fact that raw types and inference also doesn't play well together. As I said, I've played with this quite a bit and came out convinced that usability wise it's good. Note that in the proposed model, enum constants will have full generic types - e.g. Foo; it's only when you go to the supertype that the type system will say Enum. But this will only be used by APIs accepting some Enum - so we're fine, and this actually guarantees same inference results as before generification of a given enum. > > accessibility: > Widening the type is usually a big No because of the security implication. The fact that the same code code has no security bug with version n but a security hole with version n + 1 scares me. What scenario do you have in mind regarding enum constant pseudo-inner classes? > > source compatibility: > It's may not be a big issue because the JDK source doesn't use 'var'. If a code uses 'var' the sharp type will propagate more, so the JDK is not perhaps the best code to test. > > friend or foe: > the rules for raw types are brutal as you said, but it's by design, it offers maximum compatibility and doesn't allow to mix raw and generic type easily so my students detect the missing angle brackets easily (IntelliJ still doesn't warn about missing angle brackets by default :( ) > > > Now about your example, instead of being functional and wanted each Option to type their argument, you can use ugly side effects instead. > So the idea is to use a temporary class instead of a Map to store the data associated with an option. So an Option is something that takes a chunk of the command line arguments and do a side effect on the field of an instance of that temporary class. Sure, there might be other ways to get there; what I did, I did it to test usage of generic enums in a real world code base. Maurizio > > public class LineParsing { > private final HashMap>> actionMap = new HashMap<>(); > > public LineParsing with(String option, Consumer> action) { > actionMap.put(option, action); > return this; > } > > public void parse(List args) { > var it = args.iterator(); > while(it.hasNext()) { > actionMap.get(it.next()).accept(it); > } > } > > public static void main(String[] args) { > var bean = new Object() { > Path input = Path.of("input.txt"); > boolean all = false; > }; > > new LineParsing() > .with("-input", it -> bean.input = Path.of(it.next())) > .with("-all", it -> bean.all = true) > .parse(List.of(args)); > } > } > > regards, > R?mi > > > ----- Mail original ----- >> De: "Maurizio Cimadamore" >> ?: "amber-spec-experts" >> Envoy?: Mercredi 5 D?cembre 2018 17:14:59 >> Objet: enhanced enums - back from the dead? >> Hi, >> as mentioned in [1], the work on enhanced enum stopped while ago as we >> have found some interoperability issues between generic enums and >> standard enum APIs such as EnumSet/EnumMap. >> >> Recently, we have discussed a possible approach that might get us out of >> the woods, which is described in greater details here: >> >> http://cr.openjdk.java.net/~mcimadamore/amber/enhanced-enums.html >> >> We have done some internal testing to convince ourselves that, from an >> operational perspective, where we end up is indeed good. Some external >> validation might also be very helpful, which is why we're also in the >> process of releasing the internal patch we have tested internally in the >> 'enhanced-enums' amber branch (we'll need to polish it a little :-)). >> >> Assuming that, usability-wise, our story ticks all the boxes, I think it >> might be worth discussing a few points: >> >> * Do we still like the features described in JEP 301, from an >> expressiveness point of view? >> >> * Both features described in JEP 301 require some sort of massaging. On >> the one hand sharper typing of enum constants has to take care of binary >> compatibility of enum constant subclasses into account (for this reason >> we redefine accessibility of said subclasses along with their binary >> names). On the other hand, with the newly proposed approach, generic >> enums also need some language aid (treatment of raw enum constants >> supertypes). Do we feel that the steps needed in order to accommodate >> these sharp edges are worth the increase in expressive power delivered >> by JEP 301? >> >> * Our proposed treatment for generic enums raises an additional, more >> philosophical, question: what are raw types *for* and how happy are we >> in seeing more of them (in the form of raw enum types)? >> >> Cheers >> Maurizio >> >> [1] - >> http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html From forax at univ-mlv.fr Thu Dec 6 16:53:32 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Thu, 6 Dec 2018 17:53:32 +0100 (CET) Subject: enhanced enums - back from the dead? In-Reply-To: <4474050b-cfca-d40b-3712-26f8109a8456@oracle.com> References: <1970970547.807832.1544046196415.JavaMail.zimbra@u-pem.fr> <4474050b-cfca-d40b-3712-26f8109a8456@oracle.com> Message-ID: <1615585555.1032246.1544115212153.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Maurizio Cimadamore" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Jeudi 6 D?cembre 2018 09:54:09 > Objet: Re: enhanced enums - back from the dead? > Hi Remi, some comments inline. > > On 05/12/2018 21:43, Remi Forax wrote: >> Hi Maurizio, >> i think you have overlook the fact that raw types and inference also doesn't >> play well together. > As I said, I've played with this quite a bit and came out convinced that > usability wise it's good. Note that in the proposed model, enum > constants will have full generic types - e.g. Foo; it's only > when you go to the supertype that the type system will say Enum. > But this will only be used by APIs accepting some Enum - so we're > fine, and this actually guarantees same inference results as before > generification of a given enum. yes, i'm worried about Foo.values() public enum Foo> implements Comparable> { S<>(""), I<>(42); // JEP 301 mentions the diamond syntax private final T t; private Foo(T t) { this.t = t; } @Override public int compareTo(Foo o) { return t.compareTo(o.t); } public static void main(String[] args) { Arrays.stream(Foo.values()).sorted().forEach(System.out::println); } } >> >> accessibility: >> Widening the type is usually a big No because of the security implication. The >> fact that the same code code has no security bug with version n but a security >> hole with version n + 1 scares me. > What scenario do you have in mind regarding enum constant pseudo-inner classes? any scenario that is using a Lookup object >> >> source compatibility: >> It's may not be a big issue because the JDK source doesn't use 'var'. If a code >> uses 'var' the sharp type will propagate more, so the JDK is not perhaps the >> best code to test. >> >> friend or foe: >> the rules for raw types are brutal as you said, but it's by design, it offers >> maximum compatibility and doesn't allow to mix raw and generic type easily so >> my students detect the missing angle brackets easily (IntelliJ still doesn't >> warn about missing angle brackets by default :( ) >> >> >> Now about your example, instead of being functional and wanted each Option to >> type their argument, you can use ugly side effects instead. >> So the idea is to use a temporary class instead of a Map to store the data >> associated with an option. So an Option is something that takes a chunk of the >> command line arguments and do a side effect on the field of an instance of that >> temporary class. > > Sure, there might be other ways to get there; what I did, I did it to > test usage of generic enums in a real world code base. it seems to be "the use case" for generics enum. > > Maurizio R?mi > >> >> public class LineParsing { >> private final HashMap>> actionMap = >> new HashMap<>(); >> >> public LineParsing with(String option, Consumer> >> action) { >> actionMap.put(option, action); >> return this; >> } >> >> public void parse(List args) { >> var it = args.iterator(); >> while(it.hasNext()) { >> actionMap.get(it.next()).accept(it); >> } >> } >> >> public static void main(String[] args) { >> var bean = new Object() { >> Path input = Path.of("input.txt"); >> boolean all = false; >> }; >> >> new LineParsing() >> .with("-input", it -> bean.input = Path.of(it.next())) >> .with("-all", it -> bean.all = true) >> .parse(List.of(args)); >> } >> } >> >> regards, >> R?mi >> >> >> ----- Mail original ----- >>> De: "Maurizio Cimadamore" >>> ?: "amber-spec-experts" >>> Envoy?: Mercredi 5 D?cembre 2018 17:14:59 >>> Objet: enhanced enums - back from the dead? >>> Hi, >>> as mentioned in [1], the work on enhanced enum stopped while ago as we >>> have found some interoperability issues between generic enums and >>> standard enum APIs such as EnumSet/EnumMap. >>> >>> Recently, we have discussed a possible approach that might get us out of >>> the woods, which is described in greater details here: >>> >>> http://cr.openjdk.java.net/~mcimadamore/amber/enhanced-enums.html >>> >>> We have done some internal testing to convince ourselves that, from an >>> operational perspective, where we end up is indeed good. Some external >>> validation might also be very helpful, which is why we're also in the >>> process of releasing the internal patch we have tested internally in the >>> 'enhanced-enums' amber branch (we'll need to polish it a little :-)). >>> >>> Assuming that, usability-wise, our story ticks all the boxes, I think it >>> might be worth discussing a few points: >>> >>> * Do we still like the features described in JEP 301, from an >>> expressiveness point of view? >>> >>> * Both features described in JEP 301 require some sort of massaging. On >>> the one hand sharper typing of enum constants has to take care of binary >>> compatibility of enum constant subclasses into account (for this reason >>> we redefine accessibility of said subclasses along with their binary >>> names). On the other hand, with the newly proposed approach, generic >>> enums also need some language aid (treatment of raw enum constants >>> supertypes). Do we feel that the steps needed in order to accommodate >>> these sharp edges are worth the increase in expressive power delivered >>> by JEP 301? >>> >>> * Our proposed treatment for generic enums raises an additional, more >>> philosophical, question: what are raw types *for* and how happy are we >>> in seeing more of them (in the form of raw enum types)? >>> >>> Cheers >>> Maurizio >>> >>> [1] - > >> http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html From maurizio.cimadamore at oracle.com Thu Dec 6 19:23:05 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Thu, 6 Dec 2018 19:23:05 +0000 Subject: enhanced enums - back from the dead? In-Reply-To: <1615585555.1032246.1544115212153.JavaMail.zimbra@u-pem.fr> References: <1970970547.807832.1544046196415.JavaMail.zimbra@u-pem.fr> <4474050b-cfca-d40b-3712-26f8109a8456@oracle.com> <1615585555.1032246.1544115212153.JavaMail.zimbra@u-pem.fr> Message-ID: On 06/12/2018 16:53, forax at univ-mlv.fr wrote: > > ----- Mail original ----- >> De: "Maurizio Cimadamore" >> ?: "Remi Forax" >> Cc: "amber-spec-experts" >> Envoy?: Jeudi 6 D?cembre 2018 09:54:09 >> Objet: Re: enhanced enums - back from the dead? >> Hi Remi, some comments inline. >> >> On 05/12/2018 21:43, Remi Forax wrote: >>> Hi Maurizio, >>> i think you have overlook the fact that raw types and inference also doesn't >>> play well together. >> As I said, I've played with this quite a bit and came out convinced that >> usability wise it's good. Note that in the proposed model, enum >> constants will have full generic types - e.g. Foo; it's only >> when you go to the supertype that the type system will say Enum. >> But this will only be used by APIs accepting some Enum - so we're >> fine, and this actually guarantees same inference results as before >> generification of a given enum. > yes, i'm worried about Foo.values() > > public enum Foo> implements Comparable> { > S<>(""), I<>(42); // JEP 301 mentions the diamond syntax > > private final T t; > > private Foo(T t) { > this.t = t; > } > > @Override > public int compareTo(Foo o) { > return t.compareTo(o.t); > } > > public static void main(String[] args) { > Arrays.stream(Foo.values()).sorted().forEach(System.out::println); > } > } Can't quite understand what you mean - the code above is flawed in different ways; first you can't implement Comparable> - as Enum already does that and with Comparable in this case (because of the new treatment). Secondly, you can't override compareTo - which is final in enum. So, the correct example is this: import java.util.*; enum Foo> implements Comparable { ??? S(""), I(42);?? // JEP 301 mentions the diamond syntax ??? private final T t; ??? private Foo(T t) { ????? this.t = t; ??? } ??? public static void main(String[] args) { Arrays.stream(Foo.values()).sorted().forEach(System.out::println); ??? } ? } Which compiles with no issues. > >>> accessibility: >>> Widening the type is usually a big No because of the security implication. The >>> fact that the same code code has no security bug with version n but a security >>> hole with version n + 1 scares me. >> What scenario do you have in mind regarding enum constant pseudo-inner classes? > any scenario that is using a Lookup object Still not getting what is the security implication; one thing is? to say that you can reflectively inspect a class where you could not before; another is that doing so represents a vulnerability. The proposed rule says that the enum constant class gets same visibility as parent. So you are really saying that some constant class contains _security sensitive_ details, and that users relied on an *unspecified* compiler behavior that protected them, as javac made the class package-private. That seems a pretty strange argument. > >>> source compatibility: >>> It's may not be a big issue because the JDK source doesn't use 'var'. If a code >>> uses 'var' the sharp type will propagate more, so the JDK is not perhaps the >>> best code to test. >>> >>> friend or foe: >>> the rules for raw types are brutal as you said, but it's by design, it offers >>> maximum compatibility and doesn't allow to mix raw and generic type easily so >>> my students detect the missing angle brackets easily (IntelliJ still doesn't >>> warn about missing angle brackets by default :( ) >>> >>> >>> Now about your example, instead of being functional and wanted each Option to >>> type their argument, you can use ugly side effects instead. >>> So the idea is to use a temporary class instead of a Map to store the data >>> associated with an option. So an Option is something that takes a chunk of the >>> command line arguments and do a side effect on the field of an instance of that >>> temporary class. >> Sure, there might be other ways to get there; what I did, I did it to >> test usage of generic enums in a real world code base. > it seems to be "the use case" for generics enum. I get that you do not like the feature :-) Other use cases have been discussed here: http://mail.openjdk.java.net/pipermail/amber-dev/2017-April/000173.html rest assured, the JEP might bear my name, but we're not looking into this to make javac code better. Maurizio > >> Maurizio > R?mi > >>> public class LineParsing { >>> private final HashMap>> actionMap = >>> new HashMap<>(); >>> >>> public LineParsing with(String option, Consumer> >>> action) { >>> actionMap.put(option, action); >>> return this; >>> } >>> >>> public void parse(List args) { >>> var it = args.iterator(); >>> while(it.hasNext()) { >>> actionMap.get(it.next()).accept(it); >>> } >>> } >>> >>> public static void main(String[] args) { >>> var bean = new Object() { >>> Path input = Path.of("input.txt"); >>> boolean all = false; >>> }; >>> >>> new LineParsing() >>> .with("-input", it -> bean.input = Path.of(it.next())) >>> .with("-all", it -> bean.all = true) >>> .parse(List.of(args)); >>> } >>> } >>> >>> regards, >>> R?mi >>> >>> >>> ----- Mail original ----- >>>> De: "Maurizio Cimadamore" >>>> ?: "amber-spec-experts" >>>> Envoy?: Mercredi 5 D?cembre 2018 17:14:59 >>>> Objet: enhanced enums - back from the dead? >>>> Hi, >>>> as mentioned in [1], the work on enhanced enum stopped while ago as we >>>> have found some interoperability issues between generic enums and >>>> standard enum APIs such as EnumSet/EnumMap. >>>> >>>> Recently, we have discussed a possible approach that might get us out of >>>> the woods, which is described in greater details here: >>>> >>>> http://cr.openjdk.java.net/~mcimadamore/amber/enhanced-enums.html >>>> >>>> We have done some internal testing to convince ourselves that, from an >>>> operational perspective, where we end up is indeed good. Some external >>>> validation might also be very helpful, which is why we're also in the >>>> process of releasing the internal patch we have tested internally in the >>>> 'enhanced-enums' amber branch (we'll need to polish it a little :-)). >>>> >>>> Assuming that, usability-wise, our story ticks all the boxes, I think it >>>> might be worth discussing a few points: >>>> >>>> * Do we still like the features described in JEP 301, from an >>>> expressiveness point of view? >>>> >>>> * Both features described in JEP 301 require some sort of massaging. On >>>> the one hand sharper typing of enum constants has to take care of binary >>>> compatibility of enum constant subclasses into account (for this reason >>>> we redefine accessibility of said subclasses along with their binary >>>> names). On the other hand, with the newly proposed approach, generic >>>> enums also need some language aid (treatment of raw enum constants >>>> supertypes). Do we feel that the steps needed in order to accommodate >>>> these sharp edges are worth the increase in expressive power delivered >>>> by JEP 301? >>>> >>>> * Our proposed treatment for generic enums raises an additional, more >>>> philosophical, question: what are raw types *for* and how happy are we >>>> in seeing more of them (in the form of raw enum types)? >>>> >>>> Cheers >>>> Maurizio >>>> >>>> [1] - >>>> http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html From forax at univ-mlv.fr Fri Dec 7 10:26:24 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Fri, 7 Dec 2018 11:26:24 +0100 (CET) Subject: enhanced enums - back from the dead? In-Reply-To: References: <1970970547.807832.1544046196415.JavaMail.zimbra@u-pem.fr> <4474050b-cfca-d40b-3712-26f8109a8456@oracle.com> <1615585555.1032246.1544115212153.JavaMail.zimbra@u-pem.fr> Message-ID: <2092712408.1146020.1544178384809.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Maurizio Cimadamore" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Jeudi 6 D?cembre 2018 20:23:05 > Objet: Re: enhanced enums - back from the dead? > On 06/12/2018 16:53, forax at univ-mlv.fr wrote: >> >> ----- Mail original ----- >>> De: "Maurizio Cimadamore" >>> ?: "Remi Forax" >>> Cc: "amber-spec-experts" >>> Envoy?: Jeudi 6 D?cembre 2018 09:54:09 >>> Objet: Re: enhanced enums - back from the dead? >>> Hi Remi, some comments inline. >>> >>> On 05/12/2018 21:43, Remi Forax wrote: >>>> Hi Maurizio, >>>> i think you have overlook the fact that raw types and inference also doesn't >>>> play well together. >>> As I said, I've played with this quite a bit and came out convinced that >>> usability wise it's good. Note that in the proposed model, enum >>> constants will have full generic types - e.g. Foo; it's only >>> when you go to the supertype that the type system will say Enum. >>> But this will only be used by APIs accepting some Enum - so we're >>> fine, and this actually guarantees same inference results as before >>> generification of a given enum. >> yes, i'm worried about Foo.values() >> >> public enum Foo> implements Comparable> { >> S<>(""), I<>(42); // JEP 301 mentions the diamond syntax >> >> private final T t; >> >> private Foo(T t) { >> this.t = t; >> } >> >> @Override >> public int compareTo(Foo o) { >> return t.compareTo(o.t); >> } >> >> public static void main(String[] args) { >> Arrays.stream(Foo.values()).sorted().forEach(System.out::println); >> } >> } > > Can't quite understand what you mean - the code above is flawed in > different ways; first you can't implement Comparable> - as Enum > already does that and with Comparable in this case (because of the > new treatment). > > Secondly, you can't override compareTo - which is final in enum. > > So, the correct example is this: > > import java.util.*; > > enum Foo> implements Comparable { > ??? S(""), I(42);?? // JEP 301 mentions the diamond syntax > > ??? private final T t; > > ??? private Foo(T t) { > ????? this.t = t; > ??? } > > ??? public static void main(String[] args) { > Arrays.stream(Foo.values()).sorted().forEach(System.out::println); > ??? } > ? } > > > Which compiles with no issues. yes, i wanted a recursive bound and forget that an enum already implements Comparable. so let's retry public enum Foo> { S(""), I(42); // JEP 301 mentions the diamond syntax private T t; public Foo(T t) { this.t = t; } T t() { return t; } public static void main(String[] args) { Arrays.stream(values()).sorted(Comparator.comparing(Foo::t)).forEach(System.out::println); } } My original point was, when you introduce a raw type in a Stream, you end up with a warning on every methods because of the inference. While if you use the correct type, with an unbounded wildcard, the compiler stop you to do stupid things, basically, you are trading an error to a series of warning people will be happy to suppress with @SuppressWarnings. Arrays.stream(values()).sorted(Comparator.comparing(Foo::t)).forEach(System.out::println); emits a series of warnings Stream.of(S, I).sorted(Comparator.comparing(Foo::t)).forEach(System.out::println); emits an error BTW, it seems there is an issue somewhere in the compiler because Arrays.stream((Foo[])values()).sorted(Comparator.comparing(Foo::t)).forEach(System.out::println); happily compiles ?? > >> >>>> accessibility: >>>> Widening the type is usually a big No because of the security implication. The >>>> fact that the same code code has no security bug with version n but a security >>>> hole with version n + 1 scares me. >>> What scenario do you have in mind regarding enum constant pseudo-inner classes? >> any scenario that is using a Lookup object > Still not getting what is the security implication; one thing is? to say > that you can reflectively inspect a class where you could not before; > another is that doing so represents a vulnerability. The proposed rule > says that the enum constant class gets same visibility as parent. So you > are really saying that some constant class contains _security sensitive_ > details, and that users relied on an *unspecified* compiler behavior > that protected them, as javac made the class package-private. That seems > a pretty strange argument. You can inspect any class even the private one by reflection, but you can not call the constructor if the class is private, if the class is package private, the constructor is package private too so you are widening the access. >> >>>> source compatibility: >>>> It's may not be a big issue because the JDK source doesn't use 'var'. If a code >>>> uses 'var' the sharp type will propagate more, so the JDK is not perhaps the >>>> best code to test. >>>> >>>> friend or foe: >>>> the rules for raw types are brutal as you said, but it's by design, it offers >>>> maximum compatibility and doesn't allow to mix raw and generic type easily so >>>> my students detect the missing angle brackets easily (IntelliJ still doesn't >>>> warn about missing angle brackets by default :( ) >>>> >>>> >>>> Now about your example, instead of being functional and wanted each Option to >>>> type their argument, you can use ugly side effects instead. >>>> So the idea is to use a temporary class instead of a Map to store the data >>>> associated with an option. So an Option is something that takes a chunk of the >>>> command line arguments and do a side effect on the field of an instance of that >>>> temporary class. >>> Sure, there might be other ways to get there; what I did, I did it to >>> test usage of generic enums in a real world code base. >> it seems to be "the use case" for generics enum. > > I get that you do not like the feature :-) It's not that i don't like the feature, it's that for me it's a feature you can not even put in the box of the features that we could do. We start with "hey we could do this !" but there are some typing issues. Now, what your are saying is that we can use raw types to not have the typing issues, but as i said above, you are trading an error to a bunch of warnings, doesn't seems to be a good deal*. > > Other use cases have been discussed here: > > http://mail.openjdk.java.net/pipermail/amber-dev/2017-April/000173.html > > rest assured, the JEP might bear my name, but we're not looking into > this to make javac code better. correct me if i'm wrong but your other examples is something that can be replaced by the JVM Constant API. > > Maurizio R?mi * it may sound a little too Trump to your ears, sorry :) > >> >>> Maurizio >> R?mi >> >>>> public class LineParsing { >>>> private final HashMap>> actionMap = >>>> new HashMap<>(); >>>> >>>> public LineParsing with(String option, Consumer> >>>> action) { >>>> actionMap.put(option, action); >>>> return this; >>>> } >>>> >>>> public void parse(List args) { >>>> var it = args.iterator(); >>>> while(it.hasNext()) { >>>> actionMap.get(it.next()).accept(it); >>>> } >>>> } >>>> >>>> public static void main(String[] args) { >>>> var bean = new Object() { >>>> Path input = Path.of("input.txt"); >>>> boolean all = false; >>>> }; >>>> >>>> new LineParsing() >>>> .with("-input", it -> bean.input = Path.of(it.next())) >>>> .with("-all", it -> bean.all = true) >>>> .parse(List.of(args)); >>>> } >>>> } >>>> >>>> regards, >>>> R?mi >>>> >>>> >>>> ----- Mail original ----- >>>>> De: "Maurizio Cimadamore" >>>>> ?: "amber-spec-experts" >>>>> Envoy?: Mercredi 5 D?cembre 2018 17:14:59 >>>>> Objet: enhanced enums - back from the dead? >>>>> Hi, >>>>> as mentioned in [1], the work on enhanced enum stopped while ago as we >>>>> have found some interoperability issues between generic enums and >>>>> standard enum APIs such as EnumSet/EnumMap. >>>>> >>>>> Recently, we have discussed a possible approach that might get us out of >>>>> the woods, which is described in greater details here: >>>>> >>>>> http://cr.openjdk.java.net/~mcimadamore/amber/enhanced-enums.html >>>>> >>>>> We have done some internal testing to convince ourselves that, from an >>>>> operational perspective, where we end up is indeed good. Some external >>>>> validation might also be very helpful, which is why we're also in the >>>>> process of releasing the internal patch we have tested internally in the >>>>> 'enhanced-enums' amber branch (we'll need to polish it a little :-)). >>>>> >>>>> Assuming that, usability-wise, our story ticks all the boxes, I think it >>>>> might be worth discussing a few points: >>>>> >>>>> * Do we still like the features described in JEP 301, from an >>>>> expressiveness point of view? >>>>> >>>>> * Both features described in JEP 301 require some sort of massaging. On >>>>> the one hand sharper typing of enum constants has to take care of binary >>>>> compatibility of enum constant subclasses into account (for this reason >>>>> we redefine accessibility of said subclasses along with their binary >>>>> names). On the other hand, with the newly proposed approach, generic >>>>> enums also need some language aid (treatment of raw enum constants >>>>> supertypes). Do we feel that the steps needed in order to accommodate >>>>> these sharp edges are worth the increase in expressive power delivered >>>>> by JEP 301? >>>>> >>>>> * Our proposed treatment for generic enums raises an additional, more >>>>> philosophical, question: what are raw types *for* and how happy are we >>>>> in seeing more of them (in the form of raw enum types)? >>>>> >>>>> Cheers >>>>> Maurizio >>>>> >>>>> [1] - > >>>> http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html From brian.goetz at oracle.com Fri Dec 7 16:38:53 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 7 Dec 2018 11:38:53 -0500 Subject: Sealed types In-Reply-To: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> Message-ID: <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> I?ve updated the document on sealing to reflect the discussion so far. Sealed Classes *Definition.* A /sealed type/ is one for which subclassing is restricted according to guidance specified with the type?s declaration; finality can be considered a degenerate form of sealing, where no subclasses at all are permitted. Sealed types are a sensible means of modeling /algebraic sum types/ in a nominal type hierarchy; they go nicely with records (/algebraic product types/), though are also useful on their own. Sealing serves two distinct purposes. The first, and more obvious, is that it restricts who can be a subtype. This is largely a declaration-site concern, where an API owner wants to defend the integrity of their API. The other is that it potentially enables exhaustiveness analysis at the use site when switching over sealed types (and possibly other features.) This is less obvious, and the benefit is contingent on some other things, but is valuable as it enables better compile-time type checking. *Declaration.* We specify that a class is sealed by applying the |final| modifier to a class, abstract class, interface, or record, and specifying a |permits| list: |final interface Node permits A, B, C { ... } | In this explicit form, |Node| may be extended only by the types enumerated in the |permits| list (which must further be members of the same package or module.) In many situations, this may be overly explicit; if all the subtypes are declared in the same compilation unit, we may wish to permit a streamlined form of the |permits| clause, that means ?may be extended by classes in the same compilation unit.? |final interface Node permits __nestmates { ... } | (As usual, pseudo-keywords beginning with |__| are placeholders to illustrate the overall shape of the syntax.) We can think of the simpler form as merely inferring the full |permits| clause from information already present in the source file. Anonymous subclasses (and lambdas) of a sealed type are prohibited. *Exhaustiveness.* One of the benefits of sealing is that the compiler can enumerate the permitted subtypes of a sealed type; this in turn lets us perform exhaustiveness analysis when switching over patterns involving sealed types. (In the simplified form, the compiler computes the permits list by enumerating the subtypes in the nest when the nest is declared, since they are in a single compilation unit.) /Note:/ It is superficially tempting to say |permits package| or |permits module| as a shorthand, which would allow for a type to be extended by package-mates or module-mates without listing them all. However, this would undermine the compiler?s ability to reason about exhaustiveness, because packages and modules are not always co-compiled. This would achieve the desired subclassing restrictions, but not the desired ability to reason about exhaustiveness. *Classfile.* In the classfile, a sealed type is identified with an |ACC_FINAL| accessibility bit, and a |PermittedSubtypes| attribute which contains a list of permitted subtypes (similar in structure to the nestmate attributes.) Classes with |ACC_FINAL| but without |PermittedSubtypes| behave like traditional final classes. *Sealing is inherited.* Unless otherwise specified, abstract subtypes of sealed types are implicitly sealed, and concrete subtypes are implicitly final. This can be reversed by explicitly modifying the subtype with |non-final|. Unsealing a subtype in a hierarchy doesn?t undermine all the benefits of sealing, because the (possibly inferred) set of explicitly permitted subtypes still constitutes a total covering. However, users who know about unsealed subtypes can use this information to their benefit (much like we do with exceptions today; you can catch FileNotFoundException separately from IOException if you want, but don?t have to.) /Note:/ Scala made the opposite choice with respect to inheritance, requiring sealing to be opted into at all levels. This is widely believed to be a source of bugs; it is relatively rare that one actually wants a subtype of a sealed type to not be sealed, and in those cases, is best to be explicit. Not inheriting would be a simpler rule, but I?d rather not add to the list of ?things for which Java got the defaults wrong.? An example of where explicit unsealing (and private subtypes) is useful can be found in the JEP-334 API: |final interface ConstantDesc permits String, Integer, Float, Long, Double, ClassDesc, MethodTypeDesc, MethodHandleDesc, DynamicConstantDesc { } final interface ClassDesc extends ConstantDesc permits PrimitiveClassDescImpl, ReferenceClassDescImpl { } private class PrimitiveClassDescImpl implements ClassDesc { } private class ReferenceClassDescImpl implements ClassDesc { } final interface MethodTypeDesc extends ConstantDesc permits MethodTypeDescImpl { } final interface MethodHandleDesc extends ConstantDesc permits DirectMethodHandleDesc, MethodHandleDescImpl { } final interface DirectMethodHandleDesc extends MethodHandleDesc permits DirectMethodHandleDescImpl { } // designed for subclassing non-final class DynamicConstantDesc extends ConstantDesc { ... } | *Enforcement.* Both the compiler and JVM should enforce sealing, as they both enforce finality today (though from a project-management standpoint, it might be allowable for VM support to follow in a later version, rather than delaying the feature entirely.) *Accessibility.* Subtypes need not be as accessible as the sealed parent. In this case, some clients are not going to get the chance to exhaustively switch over them; they?ll have to make these switches exhaustive with a |default| clause or other total pattern. When compiling a switch over such a sealed type, the compiler can provide a useful error message (?I know this is a sealed type, but I can?t provide full exhaustiveness checking here because you can?t see all the subtypes, so you still need a default.?) *Javadoc.* The list of permitted subtypes should be incorporated into the Javadoc. Note that this is not exactly the same as the current ?All implementing classes? list that Javadoc currently includes, so a list like ?All permitted subtypes? might be added (possibly with some indication if the subtype is less accessible than the parent, or including an annotation that there exist others that are not listed.) /Open question:/ With the advent of records, which allow us to define classes in a single line, the ?one class per file? rule starts to seem both a little silly, and constrain the user?s ability to put related definitions together (which may be more readable) while exporting a flat namespace in the public API. I think it is worth considering relaxing this rule to permit for sealed classes, say: allowing public auxilliary subtypes of the primary type, if the primary type is public and sealed. ? From brian.goetz at oracle.com Fri Dec 7 17:50:55 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 7 Dec 2018 12:50:55 -0500 Subject: Sealed types In-Reply-To: <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> Message-ID: The most obvious remaining question appears to be: how do we spell "permits ". We could say "permits this", which has the advantage of being a keyword, but doesn't really mean what it says. We could say "permits local", though "local" usually means local to a method. We could say "permits nest" or "permits nested", though if we decide to allow public auxilliary subclasses in this case, then they are not likely to be nestmates, and surely not nested. We could allow the permits clause to be omitted, and be interpreted as "permits nest", but this would much more severely change the meaning of final -- people have an idea what final means, and this surely wouldn't be it. Open to other ideas... (This is one of the costs of retconning final; if we used "sealed", we wouldn't need a permits clause at all.? Overall its probably still a good tradeoff, but we do have to paint this shed acceptably.) On 12/7/2018 11:38 AM, Brian Goetz wrote: > > I?ve updated the document on sealing to reflect the discussion so far. > > > Sealed Classes > > *Definition.* A /sealed type/ is one for which subclassing is > restricted according to guidance specified with the type?s > declaration; finality can be considered a degenerate form of sealing, > where no subclasses at all are permitted. Sealed types are a sensible > means of modeling /algebraic sum types/ in a nominal type hierarchy; > they go nicely with records (/algebraic product types/), though are > also useful on their own. > > Sealing serves two distinct purposes. The first, and more obvious, is > that it restricts who can be a subtype. This is largely a > declaration-site concern, where an API owner wants to defend the > integrity of their API. The other is that it potentially enables > exhaustiveness analysis at the use site when switching over sealed > types (and possibly other features.) This is less obvious, and the > benefit is contingent on some other things, but is valuable as it > enables better compile-time type checking. > > *Declaration.* We specify that a class is sealed by applying the > |final| modifier to a class, abstract class, interface, or record, and > specifying a |permits| list: > > |final interface Node permits A, B, C { ... } | > > In this explicit form, |Node| may be extended only by the types > enumerated in the |permits| list (which must further be members of the > same package or module.) > > In many situations, this may be overly explicit; if all the subtypes > are declared in the same compilation unit, we may wish to permit a > streamlined form of the |permits| clause, that means ?may be extended > by classes in the same compilation unit.? > > |final interface Node permits __nestmates { ... } | > > (As usual, pseudo-keywords beginning with |__| are placeholders to > illustrate the overall shape of the syntax.) > > We can think of the simpler form as merely inferring the full > |permits| clause from information already present in the source file. > > Anonymous subclasses (and lambdas) of a sealed type are prohibited. > > *Exhaustiveness.* One of the benefits of sealing is that the compiler > can enumerate the permitted subtypes of a sealed type; this in turn > lets us perform exhaustiveness analysis when switching over patterns > involving sealed types. (In the simplified form, the compiler computes > the permits list by enumerating the subtypes in the nest when the nest > is declared, since they are in a single compilation unit.) > > /Note:/ It is superficially tempting to say |permits package| or > |permits module| as a shorthand, which would allow for a type to be > extended by package-mates or module-mates without listing them all. > However, this would undermine the compiler?s ability to reason about > exhaustiveness, because packages and modules are not always > co-compiled. This would achieve the desired subclassing restrictions, > but not the desired ability to reason about exhaustiveness. > > *Classfile.* In the classfile, a sealed type is identified with an > |ACC_FINAL| accessibility bit, and a |PermittedSubtypes| attribute > which contains a list of permitted subtypes (similar in structure to > the nestmate attributes.) Classes with |ACC_FINAL| but without > |PermittedSubtypes| behave like traditional final classes. > > *Sealing is inherited.* Unless otherwise specified, abstract subtypes > of sealed types are implicitly sealed, and concrete subtypes are > implicitly final. This can be reversed by explicitly modifying the > subtype with |non-final|. > > Unsealing a subtype in a hierarchy doesn?t undermine all the benefits > of sealing, because the (possibly inferred) set of explicitly > permitted subtypes still constitutes a total covering. However, users > who know about unsealed subtypes can use this information to their > benefit (much like we do with exceptions today; you can catch > FileNotFoundException separately from IOException if you want, but > don?t have to.) > /Note:/ Scala made the opposite choice with respect to inheritance, > requiring sealing to be opted into at all levels. This is widely > believed to be a source of bugs; it is relatively rare that one > actually wants a subtype of a sealed type to not be sealed, and in > those cases, is best to be explicit. Not inheriting would be a simpler > rule, but I?d rather not add to the list of ?things for which Java got > the defaults wrong.? > > An example of where explicit unsealing (and private subtypes) is > useful can be found in the JEP-334 API: > > |final interface ConstantDesc permits String, Integer, Float, Long, > Double, ClassDesc, MethodTypeDesc, MethodHandleDesc, > DynamicConstantDesc { } final interface ClassDesc extends ConstantDesc > permits PrimitiveClassDescImpl, ReferenceClassDescImpl { } private > class PrimitiveClassDescImpl implements ClassDesc { } private class > ReferenceClassDescImpl implements ClassDesc { } final interface > MethodTypeDesc extends ConstantDesc permits MethodTypeDescImpl { } > final interface MethodHandleDesc extends ConstantDesc permits > DirectMethodHandleDesc, MethodHandleDescImpl { } final interface > DirectMethodHandleDesc extends MethodHandleDesc permits > DirectMethodHandleDescImpl { } // designed for subclassing non-final > class DynamicConstantDesc extends ConstantDesc { ... } | > > *Enforcement.* Both the compiler and JVM should enforce sealing, as > they both enforce finality today (though from a project-management > standpoint, it might be allowable for VM support to follow in a later > version, rather than delaying the feature entirely.) > > *Accessibility.* Subtypes need not be as accessible as the sealed > parent. In this case, some clients are not going to get the chance to > exhaustively switch over them; they?ll have to make these switches > exhaustive with a |default| clause or other total pattern. When > compiling a switch over such a sealed type, the compiler can provide a > useful error message (?I know this is a sealed type, but I can?t > provide full exhaustiveness checking here because you can?t see all > the subtypes, so you still need a default.?) > > *Javadoc.* The list of permitted subtypes should be incorporated into > the Javadoc. Note that this is not exactly the same as the current > ?All implementing classes? list that Javadoc currently includes, so a > list like ?All permitted subtypes? might be added (possibly with some > indication if the subtype is less accessible than the parent, or > including an annotation that there exist others that are not listed.) > > /Open question:/ With the advent of records, which allow us to define > classes in a single line, the ?one class per file? rule starts to seem > both a little silly, and constrain the user?s ability to put related > definitions together (which may be more readable) while exporting a > flat namespace in the public API. I think it is worth considering > relaxing this rule to permit for sealed classes, say: allowing public > auxilliary subtypes of the primary type, if the primary type is public > and sealed. > > ? From dl at cs.oswego.edu Fri Dec 7 18:16:51 2018 From: dl at cs.oswego.edu (Doug Lea) Date: Fri, 7 Dec 2018 13:16:51 -0500 Subject: Sealed types In-Reply-To: References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> Message-ID: <4153d8b5-16a6-6be8-0c6b-bbc2319d220b@cs.oswego.edu> Some idle bikeshedding... On 12/7/18 12:50 PM, Brian Goetz wrote: > The most obvious remaining question appears to be: how do we spell > "permits ". Maybe "permits" -> "exclusivelyIncludes" or just "exclusively"? These seem less likely to be misinterpreted and also less likely to lead to any confusion with not-rare use of "permits" as a variable (in semaphores etc). Is there any reason except convenience to use the implicit version for classes in the same unit? You'd guess that without it, IDEs would soon offer to help fill in the lists. -Doug > > We could say "permits this", which has the advantage of being a keyword, > but doesn't really mean what it says.? > > We could say "permits local", though "local" usually means local to a > method.? > > We could say "permits nest" or "permits nested", though if we decide to > allow public auxilliary subclasses in this case, then they are not > likely to be nestmates, and surely not nested. > > We could allow the permits clause to be omitted, and be interpreted as > "permits nest", but this would much more severely change the meaning of > final -- people have an idea what final means, and this surely wouldn't > be it.? > > Open to other ideas... > > (This is one of the costs of retconning final; if we used "sealed", we > wouldn't need a permits clause at all.? Overall its probably still a > good tradeoff, but we do have to paint this shed acceptably.)? > > > > On 12/7/2018 11:38 AM, Brian Goetz wrote: >> >> I?ve updated the document on sealing to reflect the discussion so far. >> >> >> Sealed Classes >> >> *Definition.* A /sealed type/ is one for which subclassing is >> restricted according to guidance specified with the type?s >> declaration; finality can be considered a degenerate form of sealing, >> where no subclasses at all are permitted. Sealed types are a sensible >> means of modeling /algebraic sum types/ in a nominal type hierarchy; >> they go nicely with records (/algebraic product types/), though are >> also useful on their own. >> >> Sealing serves two distinct purposes. The first, and more obvious, is >> that it restricts who can be a subtype. This is largely a >> declaration-site concern, where an API owner wants to defend the >> integrity of their API. The other is that it potentially enables >> exhaustiveness analysis at the use site when switching over sealed >> types (and possibly other features.) This is less obvious, and the >> benefit is contingent on some other things, but is valuable as it >> enables better compile-time type checking. >> >> *Declaration.* We specify that a class is sealed by applying the >> |final| modifier to a class, abstract class, interface, or record, and >> specifying a |permits| list: >> >> |final interface Node permits A, B, C { ... } | >> >> In this explicit form, |Node| may be extended only by the types >> enumerated in the |permits| list (which must further be members of the >> same package or module.) >> >> In many situations, this may be overly explicit; if all the subtypes >> are declared in the same compilation unit, we may wish to permit a >> streamlined form of the |permits| clause, that means ?may be extended >> by classes in the same compilation unit.? >> >> |final interface Node permits __nestmates { ... } | >> >> (As usual, pseudo-keywords beginning with |__| are placeholders to >> illustrate the overall shape of the syntax.) >> >> We can think of the simpler form as merely inferring the full >> |permits| clause from information already present in the source file. >> >> Anonymous subclasses (and lambdas) of a sealed type are prohibited. >> >> *Exhaustiveness.* One of the benefits of sealing is that the compiler >> can enumerate the permitted subtypes of a sealed type; this in turn >> lets us perform exhaustiveness analysis when switching over patterns >> involving sealed types. (In the simplified form, the compiler computes >> the permits list by enumerating the subtypes in the nest when the nest >> is declared, since they are in a single compilation unit.) >> >> /Note:/ It is superficially tempting to say |permits package| or >> |permits module| as a shorthand, which would allow for a type to be >> extended by package-mates or module-mates without listing them all. >> However, this would undermine the compiler?s ability to reason about >> exhaustiveness, because packages and modules are not always >> co-compiled. This would achieve the desired subclassing restrictions, >> but not the desired ability to reason about exhaustiveness. >> >> *Classfile.* In the classfile, a sealed type is identified with an >> |ACC_FINAL| accessibility bit, and a |PermittedSubtypes| attribute >> which contains a list of permitted subtypes (similar in structure to >> the nestmate attributes.) Classes with |ACC_FINAL| but without >> |PermittedSubtypes| behave like traditional final classes. >> >> *Sealing is inherited.* Unless otherwise specified, abstract subtypes >> of sealed types are implicitly sealed, and concrete subtypes are >> implicitly final. This can be reversed by explicitly modifying the >> subtype with |non-final|. >> >> Unsealing a subtype in a hierarchy doesn?t undermine all the benefits >> of sealing, because the (possibly inferred) set of explicitly >> permitted subtypes still constitutes a total covering. However, users >> who know about unsealed subtypes can use this information to their >> benefit (much like we do with exceptions today; you can catch >> FileNotFoundException separately from IOException if you want, but >> don?t have to.) >> /Note:/ Scala made the opposite choice with respect to inheritance, >> requiring sealing to be opted into at all levels. This is widely >> believed to be a source of bugs; it is relatively rare that one >> actually wants a subtype of a sealed type to not be sealed, and in >> those cases, is best to be explicit. Not inheriting would be a simpler >> rule, but I?d rather not add to the list of ?things for which Java got >> the defaults wrong.? >> >> An example of where explicit unsealing (and private subtypes) is >> useful can be found in the JEP-334 API: >> >> |final interface ConstantDesc permits String, Integer, Float, Long, >> Double, ClassDesc, MethodTypeDesc, MethodHandleDesc, >> DynamicConstantDesc { } final interface ClassDesc extends ConstantDesc >> permits PrimitiveClassDescImpl, ReferenceClassDescImpl { } private >> class PrimitiveClassDescImpl implements ClassDesc { } private class >> ReferenceClassDescImpl implements ClassDesc { } final interface >> MethodTypeDesc extends ConstantDesc permits MethodTypeDescImpl { } >> final interface MethodHandleDesc extends ConstantDesc permits >> DirectMethodHandleDesc, MethodHandleDescImpl { } final interface >> DirectMethodHandleDesc extends MethodHandleDesc permits >> DirectMethodHandleDescImpl { } // designed for subclassing non-final >> class DynamicConstantDesc extends ConstantDesc { ... } | >> >> *Enforcement.* Both the compiler and JVM should enforce sealing, as >> they both enforce finality today (though from a project-management >> standpoint, it might be allowable for VM support to follow in a later >> version, rather than delaying the feature entirely.) >> >> *Accessibility.* Subtypes need not be as accessible as the sealed >> parent. In this case, some clients are not going to get the chance to >> exhaustively switch over them; they?ll have to make these switches >> exhaustive with a |default| clause or other total pattern. When >> compiling a switch over such a sealed type, the compiler can provide a >> useful error message (?I know this is a sealed type, but I can?t >> provide full exhaustiveness checking here because you can?t see all >> the subtypes, so you still need a default.?) >> >> *Javadoc.* The list of permitted subtypes should be incorporated into >> the Javadoc. Note that this is not exactly the same as the current >> ?All implementing classes? list that Javadoc currently includes, so a >> list like ?All permitted subtypes? might be added (possibly with some >> indication if the subtype is less accessible than the parent, or >> including an annotation that there exist others that are not listed.) >> >> /Open question:/ With the advent of records, which allow us to define >> classes in a single line, the ?one class per file? rule starts to seem >> both a little silly, and constrain the user?s ability to put related >> definitions together (which may be more readable) while exporting a >> flat namespace in the public API. I think it is worth considering >> relaxing this rule to permit for sealed classes, say: allowing public >> auxilliary subtypes of the primary type, if the primary type is public >> and sealed. >> >> ? > From brian.goetz at oracle.com Fri Dec 7 18:43:39 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 7 Dec 2018 13:43:39 -0500 Subject: Sealed types In-Reply-To: <4153d8b5-16a6-6be8-0c6b-bbc2319d220b@cs.oswego.edu> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <4153d8b5-16a6-6be8-0c6b-bbc2319d220b@cs.oswego.edu> Message-ID: <369e9889-6fd2-d605-d5fa-520ad5ff4b41@oracle.com> > Maybe "permits" -> "exclusivelyIncludes" or just "exclusively"? These > seem less likely to be misinterpreted and also less likely to lead to > any confusion with not-rare use of "permits" as a variable (in > semaphores etc). Good thought. > Is there any reason except convenience to use the implicit version for > classes in the same unit? You'd guess that without it, IDEs would soon > offer to help fill in the lists. > It?s convenience for sure, but users might well think it?s a little excessive to have to say: |final interface Node permits IntNode, PlusNode, MulNode, NegNode, ParenNode { } record IntNode(int value) implements Node; record PlusNode(Node left, Node right) implements Node; record PlusNode(Node left, Node right) implements Node; record NegNode(Node node) implements Node; record ParenNode(Node node) implements Node; | When the whole sum-of-products is this compact, saying ?IntNode? twice will feel like the same sort of Java verbosity everyone complains about. And in typical Visitor situations, there are more than a handful of subtypes, meaning that the ?permits? clause will be pretty long. ? From guy.steele at oracle.com Fri Dec 7 18:27:16 2018 From: guy.steele at oracle.com (Guy Steele) Date: Fri, 7 Dec 2018 13:27:16 -0500 Subject: Sealed types In-Reply-To: References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> Message-ID: <6C5D7EE3-9221-48D8-B85E-8853A545B27A@oracle.com> How about ?permits class?? (if you don?t like that, then all I have left to offer are ?permits catch? and ?permits goto?. :-) > On Dec 7, 2018, at 12:50 PM, Brian Goetz wrote: > > The most obvious remaining question appears to be: how do we spell "permits ". > > We could say "permits this", which has the advantage of being a keyword, but doesn't really mean what it says. > > We could say "permits local", though "local" usually means local to a method. > > We could say "permits nest" or "permits nested", though if we decide to allow public auxilliary subclasses in this case, then they are not likely to be nestmates, and surely not nested. > > We could allow the permits clause to be omitted, and be interpreted as "permits nest", but this would much more severely change the meaning of final -- people have an idea what final means, and this surely wouldn't be it. > > Open to other ideas... > > (This is one of the costs of retconning final; if we used "sealed", we wouldn't need a permits clause at all. Overall its probably still a good tradeoff, but we do have to paint this shed acceptably.) > > > > On 12/7/2018 11:38 AM, Brian Goetz wrote: >> I?ve updated the document on sealing to reflect the discussion so far. >> Sealed Classes >> >> Definition. A sealed type is one for which subclassing is restricted according to guidance specified with the type?s declaration; finality can be considered a degenerate form of sealing, where no subclasses at all are permitted. Sealed types are a sensible means of modeling algebraic sum types in a nominal type hierarchy; they go nicely with records (algebraic product types), though are also useful on their own. >> >> Sealing serves two distinct purposes. The first, and more obvious, is that it restricts who can be a subtype. This is largely a declaration-site concern, where an API owner wants to defend the integrity of their API. The other is that it potentially enables exhaustiveness analysis at the use site when switching over sealed types (and possibly other features.) This is less obvious, and the benefit is contingent on some other things, but is valuable as it enables better compile-time type checking. >> >> Declaration. We specify that a class is sealed by applying the final modifier to a class, abstract class, interface, or record, and specifying a permits list: >> >> final interface Node >> permits A, B, C { ... } >> In this explicit form, Node may be extended only by the types enumerated in the permits list (which must further be members of the same package or module.) >> >> In many situations, this may be overly explicit; if all the subtypes are declared in the same compilation unit, we may wish to permit a streamlined form of the permits clause, that means ?may be extended by classes in the same compilation unit.? >> >> final interface Node >> permits __nestmates { ... } >> (As usual, pseudo-keywords beginning with __ are placeholders to illustrate the overall shape of the syntax.) >> >> We can think of the simpler form as merely inferring the full permits clause from information already present in the source file. >> >> Anonymous subclasses (and lambdas) of a sealed type are prohibited. >> >> Exhaustiveness. One of the benefits of sealing is that the compiler can enumerate the permitted subtypes of a sealed type; this in turn lets us perform exhaustiveness analysis when switching over patterns involving sealed types. (In the simplified form, the compiler computes the permits list by enumerating the subtypes in the nest when the nest is declared, since they are in a single compilation unit.) >> >> Note: It is superficially tempting to say permits package or permits module as a shorthand, which would allow for a type to be extended by package-mates or module-mates without listing them all. However, this would undermine the compiler?s ability to reason about exhaustiveness, because packages and modules are not always co-compiled. This would achieve the desired subclassing restrictions, but not the desired ability to reason about exhaustiveness. >> >> Classfile. In the classfile, a sealed type is identified with an ACC_FINAL accessibility bit, and a PermittedSubtypes attribute which contains a list of permitted subtypes (similar in structure to the nestmate attributes.) Classes with ACC_FINAL but without PermittedSubtypes behave like traditional final classes. >> >> Sealing is inherited. Unless otherwise specified, abstract subtypes of sealed types are implicitly sealed, and concrete subtypes are implicitly final. This can be reversed by explicitly modifying the subtype with non-final. >> >> Unsealing a subtype in a hierarchy doesn?t undermine all the benefits of sealing, because the (possibly inferred) set of explicitly permitted subtypes still constitutes a total covering. However, users who know about unsealed subtypes can use this information to their benefit (much like we do with exceptions today; you can catch FileNotFoundException separately from IOException if you want, but don?t have to.) >> Note: Scala made the opposite choice with respect to inheritance, requiring sealing to be opted into at all levels. This is widely believed to be a source of bugs; it is relatively rare that one actually wants a subtype of a sealed type to not be sealed, and in those cases, is best to be explicit. Not inheriting would be a simpler rule, but I?d rather not add to the list of ?things for which Java got the defaults wrong.? >> >> An example of where explicit unsealing (and private subtypes) is useful can be found in the JEP-334 API: >> >> final interface ConstantDesc >> permits String, Integer, Float, Long, Double, >> ClassDesc, MethodTypeDesc, MethodHandleDesc, >> DynamicConstantDesc { } >> >> final interface ClassDesc extends ConstantDesc >> permits PrimitiveClassDescImpl, ReferenceClassDescImpl { } >> >> private class PrimitiveClassDescImpl implements ClassDesc { } >> private class ReferenceClassDescImpl implements ClassDesc { } >> final interface MethodTypeDesc extends ConstantDesc >> permits MethodTypeDescImpl { } >> >> final interface MethodHandleDesc extends ConstantDesc >> permits DirectMethodHandleDesc, MethodHandleDescImpl { } >> final interface DirectMethodHandleDesc extends MethodHandleDesc >> permits DirectMethodHandleDescImpl { } >> >> // designed for subclassing >> non-final class DynamicConstantDesc extends ConstantDesc { ... } >> Enforcement. Both the compiler and JVM should enforce sealing, as they both enforce finality today (though from a project-management standpoint, it might be allowable for VM support to follow in a later version, rather than delaying the feature entirely.) >> >> Accessibility. Subtypes need not be as accessible as the sealed parent. In this case, some clients are not going to get the chance to exhaustively switch over them; they?ll have to make these switches exhaustive with a default clause or other total pattern. When compiling a switch over such a sealed type, the compiler can provide a useful error message (?I know this is a sealed type, but I can?t provide full exhaustiveness checking here because you can?t see all the subtypes, so you still need a default.?) >> >> Javadoc. The list of permitted subtypes should be incorporated into the Javadoc. Note that this is not exactly the same as the current ?All implementing classes? list that Javadoc currently includes, so a list like ?All permitted subtypes? might be added (possibly with some indication if the subtype is less accessible than the parent, or including an annotation that there exist others that are not listed.) >> >> Open question: With the advent of records, which allow us to define classes in a single line, the ?one class per file? rule starts to seem both a little silly, and constrain the user?s ability to put related definitions together (which may be more readable) while exporting a flat namespace in the public API. I think it is worth considering relaxing this rule to permit for sealed classes, say: allowing public auxilliary subtypes of the primary type, if the primary type is public and sealed. >> > From guy.steele at oracle.com Fri Dec 7 18:39:14 2018 From: guy.steele at oracle.com (Guy Steele) Date: Fri, 7 Dec 2018 13:39:14 -0500 Subject: Sealed types In-Reply-To: <3598a51f-eae3-cbbc-396a-20a5801223aa@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <6C5D7EE3-9221-48D8-B85E-8853A545B27A@oracle.com> <3598a51f-eae3-cbbc-396a-20a5801223aa@oracle.com> Message-ID: Has a nice ring to it. > On Dec 7, 2018, at 1:57 PM, Brian Goetz wrote: > > And if we're trying to save on new keywords: > > permits this class > > :) From brian.goetz at oracle.com Fri Dec 7 18:57:07 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 7 Dec 2018 13:57:07 -0500 Subject: Sealed types In-Reply-To: <6C5D7EE3-9221-48D8-B85E-8853A545B27A@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <6C5D7EE3-9221-48D8-B85E-8853A545B27A@oracle.com> Message-ID: <3598a51f-eae3-cbbc-396a-20a5801223aa@oracle.com> And if we're trying to save on new keywords: ??? permits this class :) On 12/7/2018 1:27 PM, Guy Steele wrote: > How about ?permits class?? > > (if you don?t like that, then all I have left to offer are ?permits > catch? and ?permits goto?. ?:-) > >> On Dec 7, 2018, at 12:50 PM, Brian Goetz > > wrote: >> >> The most obvious remaining question appears to be: how do we spell >> "permits ". >> >> We could say "permits this", which has the advantage of being a >> keyword, but doesn't really mean what it says. >> >> We could say "permits local", though "local" usually means local to a >> method. >> >> We could say "permits nest" or "permits nested", though if we decide >> to allow public auxilliary subclasses in this case, then they are not >> likely to be nestmates, and surely not nested. >> >> We could allow the permits clause to be omitted, and be interpreted >> as "permits nest", but this would much more severely change the >> meaning of final -- people have an idea what final means, and this >> surely wouldn't be it. >> >> Open to other ideas... >> >> (This is one of the costs of retconning final; if we used "sealed", >> we wouldn't need a permits clause at all.? Overall its probably still >> a good tradeoff, but we do have to paint this shed acceptably.) >> >> >> >> On 12/7/2018 11:38 AM, Brian Goetz wrote: >>> >>> I?ve updated the document on sealing to reflect the discussion so far. >>> >>> >>> Sealed Classes >>> >>> *Definition.* A /sealed type/ is one for which subclassing is >>> restricted according to guidance specified with the type?s >>> declaration; finality can be considered a degenerate form of >>> sealing, where no subclasses at all are permitted. Sealed types are >>> a sensible means of modeling /algebraic sum types/ in a nominal type >>> hierarchy; they go nicely with records (/algebraic product types/), >>> though are also useful on their own. >>> >>> Sealing serves two distinct purposes. The first, and more obvious, >>> is that it restricts who can be a subtype. This is largely a >>> declaration-site concern, where an API owner wants to defend the >>> integrity of their API. The other is that it potentially enables >>> exhaustiveness analysis at the use site when switching over sealed >>> types (and possibly other features.) This is less obvious, and the >>> benefit is contingent on some other things, but is valuable as it >>> enables better compile-time type checking. >>> >>> *Declaration.* We specify that a class is sealed by applying the >>> |final| modifier to a class, abstract class, interface, or record, >>> and specifying a |permits| list: >>> >>> |final interface Node permits A, B, C { ... } | >>> >>> In this explicit form, |Node| may be extended only by the types >>> enumerated in the |permits| list (which must further be members of >>> the same package or module.) >>> >>> In many situations, this may be overly explicit; if all the subtypes >>> are declared in the same compilation unit, we may wish to permit a >>> streamlined form of the |permits| clause, that means ?may be >>> extended by classes in the same compilation unit.? >>> >>> |final interface Node permits __nestmates { ... } | >>> >>> (As usual, pseudo-keywords beginning with |__| are placeholders to >>> illustrate the overall shape of the syntax.) >>> >>> We can think of the simpler form as merely inferring the full >>> |permits| clause from information already present in the source file. >>> >>> Anonymous subclasses (and lambdas) of a sealed type are prohibited. >>> >>> *Exhaustiveness.* One of the benefits of sealing is that the >>> compiler can enumerate the permitted subtypes of a sealed type; this >>> in turn lets us perform exhaustiveness analysis when switching over >>> patterns involving sealed types. (In the simplified form, the >>> compiler computes the permits list by enumerating the subtypes in >>> the nest when the nest is declared, since they are in a single >>> compilation unit.) >>> >>> /Note:/ It is superficially tempting to say |permits package| or >>> |permits module| as a shorthand, which would allow for a type to be >>> extended by package-mates or module-mates without listing them all. >>> However, this would undermine the compiler?s ability to reason about >>> exhaustiveness, because packages and modules are not always >>> co-compiled. This would achieve the desired subclassing >>> restrictions, but not the desired ability to reason about >>> exhaustiveness. >>> >>> *Classfile.* In the classfile, a sealed type is identified with an >>> |ACC_FINAL| accessibility bit, and a |PermittedSubtypes| attribute >>> which contains a list of permitted subtypes (similar in structure to >>> the nestmate attributes.) Classes with |ACC_FINAL| but without >>> |PermittedSubtypes| behave like traditional final classes. >>> >>> *Sealing is inherited.* Unless otherwise specified, abstract >>> subtypes of sealed types are implicitly sealed, and concrete >>> subtypes are implicitly final. This can be reversed by explicitly >>> modifying the subtype with |non-final|. >>> >>> Unsealing a subtype in a hierarchy doesn?t undermine all the >>> benefits of sealing, because the (possibly inferred) set of >>> explicitly permitted subtypes still constitutes a total covering. >>> However, users who know about unsealed subtypes can use this >>> information to their benefit (much like we do with exceptions today; >>> you can catch FileNotFoundException separately from IOException if >>> you want, but don?t have to.) >>> /Note:/ Scala made the opposite choice with respect to inheritance, >>> requiring sealing to be opted into at all levels. This is widely >>> believed to be a source of bugs; it is relatively rare that one >>> actually wants a subtype of a sealed type to not be sealed, and in >>> those cases, is best to be explicit. Not inheriting would be a >>> simpler rule, but I?d rather not add to the list of ?things for >>> which Java got the defaults wrong.? >>> >>> An example of where explicit unsealing (and private subtypes) is >>> useful can be found in the JEP-334 API: >>> >>> |final interface ConstantDesc permits String, Integer, Float, Long, >>> Double, ClassDesc, MethodTypeDesc, MethodHandleDesc, >>> DynamicConstantDesc { } final interface ClassDesc extends >>> ConstantDesc permits PrimitiveClassDescImpl, ReferenceClassDescImpl >>> { } private class PrimitiveClassDescImpl implements ClassDesc { } >>> private class ReferenceClassDescImpl implements ClassDesc { } final >>> interface MethodTypeDesc extends ConstantDesc permits >>> MethodTypeDescImpl { } final interface MethodHandleDesc extends >>> ConstantDesc permits DirectMethodHandleDesc, MethodHandleDescImpl { >>> } final interface DirectMethodHandleDesc extends MethodHandleDesc >>> permits DirectMethodHandleDescImpl { } // designed for subclassing >>> non-final class DynamicConstantDesc extends ConstantDesc { ... } | >>> >>> *Enforcement.* Both the compiler and JVM should enforce sealing, as >>> they both enforce finality today (though from a project-management >>> standpoint, it might be allowable for VM support to follow in a >>> later version, rather than delaying the feature entirely.) >>> >>> *Accessibility.* Subtypes need not be as accessible as the sealed >>> parent. In this case, some clients are not going to get the chance >>> to exhaustively switch over them; they?ll have to make these >>> switches exhaustive with a |default| clause or other total pattern. >>> When compiling a switch over such a sealed type, the compiler can >>> provide a useful error message (?I know this is a sealed type, but I >>> can?t provide full exhaustiveness checking here because you can?t >>> see all the subtypes, so you still need a default.?) >>> >>> *Javadoc.* The list of permitted subtypes should be incorporated >>> into the Javadoc. Note that this is not exactly the same as the >>> current ?All implementing classes? list that Javadoc currently >>> includes, so a list like ?All permitted subtypes? might be added >>> (possibly with some indication if the subtype is less accessible >>> than the parent, or including an annotation that there exist others >>> that are not listed.) >>> >>> /Open question:/ With the advent of records, which allow us to >>> define classes in a single line, the ?one class per file? rule >>> starts to seem both a little silly, and constrain the user?s ability >>> to put related definitions together (which may be more readable) >>> while exporting a flat namespace in the public API. I think it is >>> worth considering relaxing this rule to permit for sealed classes, >>> say: allowing public auxilliary subtypes of the primary type, if the >>> primary type is public and sealed. >>> >>> ? >> > From guy.steele at oracle.com Fri Dec 7 18:40:55 2018 From: guy.steele at oracle.com (Guy Steele) Date: Fri, 7 Dec 2018 13:40:55 -0500 Subject: Sealed types In-Reply-To: References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <6C5D7EE3-9221-48D8-B85E-8853A545B27A@oracle.com> <3598a51f-eae3-cbbc-396a-20a5801223aa@oracle.com> Message-ID: <3ECB955D-E27B-470E-9050-D8EF8B3EAA16@oracle.com> More seriously (?): `permits ...` . > On Dec 7, 2018, at 1:39 PM, Guy Steele wrote: > > Has a nice ring to it. > >> On Dec 7, 2018, at 1:57 PM, Brian Goetz > wrote: >> >> And if we're trying to save on new keywords: >> >> permits this class >> >> :) > From forax at univ-mlv.fr Fri Dec 7 19:42:29 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 7 Dec 2018 20:42:29 +0100 (CET) Subject: Sealed types In-Reply-To: <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> Message-ID: <1413705338.44635.1544211749780.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "amber-spec-experts" > Envoy?: Vendredi 7 D?cembre 2018 17:38:53 > Objet: Re: Sealed types > I?ve updated the document on sealing to reflect the discussion so far. > Sealed Classes > Definition. A sealed type is one for which subclassing is restricted according > to guidance specified with the type?s declaration; finality can be considered a > degenerate form of sealing, where no subclasses at all are permitted. Sealed > types are a sensible means of modeling algebraic sum types in a nominal type > hierarchy; they go nicely with records ( algebraic product types ), though are > also useful on their own. > Sealing serves two distinct purposes. The first, and more obvious, is that it > restricts who can be a subtype. This is largely a declaration-site concern, > where an API owner wants to defend the integrity of their API. The other is > that it potentially enables exhaustiveness analysis at the use site when > switching over sealed types (and possibly other features.) This is less > obvious, and the benefit is contingent on some other things, but is valuable as > it enables better compile-time type checking. > Declaration. We specify that a class is sealed by applying the final modifier to > a class, abstract class, interface, or record, and specifying a permits list: > final interface Node > permits A, B, C { ... } > In this explicit form, Node may be extended only by the types enumerated in the > permits list (which must further be members of the same package or module.) > In many situations, this may be overly explicit; if all the subtypes are > declared in the same compilation unit, we may wish to permit a streamlined form > of the permits clause, that means ?may be extended by classes in the same > compilation unit.? > final interface Node > permits __nestmates { ... } > (As usual, pseudo-keywords beginning with __ are placeholders to illustrate the > overall shape of the syntax.) > We can think of the simpler form as merely inferring the full permits clause > from information already present in the source file. > Anonymous subclasses (and lambdas) of a sealed type are prohibited. I suppose that by anonymous, you mean classes defined in methods named or not. Not supporting anonymous classes still seems backward to me. By example, this doesn't work: final interface Option { default T orElseThrow() { throw new NoSuchElementException(); } public static Option empty() { return (Option)Empty.EMPTY; } public static Option some(T value) { return new Option<>() { public T orElseThrow() { return value; } }; } } private class Empty implements Option { private static final Empty EMPTY = new Empty<>(); } while this works: final interface Option { default T orElseThrow() { throw new NoSuchElementException(); } public static Option empty() { return (Option)Empty.EMPTY; } public static Option some(T value) { return new Some<>(value); } } private class Empty implements Option { private static final Empty EMPTY = new Empty<>(); } private class Some implements Option { private final T value; public Some(T value) { this.value = value; } public T orElseThrow() { return value; } } basically forcing me to write the code that the compiler generates for me when i use an anonymous class. So sorry to be stubborn about that issue but why a stable name is a requirement given that private classes are supported ? > Exhaustiveness. One of the benefits of sealing is that the compiler can > enumerate the permitted subtypes of a sealed type; this in turn lets us perform > exhaustiveness analysis when switching over patterns involving sealed types. > (In the simplified form, the compiler computes the permits list by enumerating > the subtypes in the nest when the nest is declared, since they are in a single > compilation unit.) > Note: It is superficially tempting to say permits package or permits module as a > shorthand, which would allow for a type to be extended by package-mates or > module-mates without listing them all. However, this would undermine the > compiler?s ability to reason about exhaustiveness, because packages and modules > are not always co-compiled. This would achieve the desired subclassing > restrictions, but not the desired ability to reason about exhaustiveness. yes ! > Classfile. In the classfile, a sealed type is identified with an ACC_FINAL > accessibility bit, and a PermittedSubtypes attribute which contains a list of > permitted subtypes (similar in structure to the nestmate attributes.) Classes > with ACC_FINAL but without PermittedSubtypes behave like traditional final > classes. > Sealing is inherited. Unless otherwise specified, abstract subtypes of sealed > types are implicitly sealed, and concrete subtypes are implicitly final. This > can be reversed by explicitly modifying the subtype with non-final . we may re-use 'open' as dual of final here ? > Unsealing a subtype in a hierarchy doesn?t undermine all the benefits of > sealing, because the (possibly inferred) set of explicitly permitted subtypes > still constitutes a total covering. However, users who know about unsealed > subtypes can use this information to their benefit (much like we do with > exceptions today; you can catch FileNotFoundException separately from > IOException if you want, but don?t have to.) > Note: Scala made the opposite choice with respect to inheritance, requiring > sealing to be opted into at all levels. This is widely believed to be a source > of bugs; it is relatively rare that one actually wants a subtype of a sealed > type to not be sealed, and in those cases, is best to be explicit. Not > inheriting would be a simpler rule, but I?d rather not add to the list of > ?things for which Java got the defaults wrong.? > An example of where explicit unsealing (and private subtypes) is useful can be > found in the JEP-334 API: > final interface ConstantDesc > permits String, Integer, Float, Long, Double, > ClassDesc, MethodTypeDesc, MethodHandleDesc, > DynamicConstantDesc { } > final interface ClassDesc extends ConstantDesc > permits PrimitiveClassDescImpl, ReferenceClassDescImpl { } > private class PrimitiveClassDescImpl implements ClassDesc { } > private class ReferenceClassDescImpl implements ClassDesc { } > final interface MethodTypeDesc extends ConstantDesc > permits MethodTypeDescImpl { } > final interface MethodHandleDesc extends ConstantDesc > permits DirectMethodHandleDesc, MethodHandleDescImpl { } > final interface DirectMethodHandleDesc extends MethodHandleDesc > permits DirectMethodHandleDescImpl { } > // designed for subclassing > non-final class DynamicConstantDesc extends ConstantDesc { ... } > Enforcement. Both the compiler and JVM should enforce sealing, as they both > enforce finality today (though from a project-management standpoint, it might > be allowable for VM support to follow in a later version, rather than delaying > the feature entirely.) > Accessibility. Subtypes need not be as accessible as the sealed parent. In this > case, some clients are not going to get the chance to exhaustively switch over > them; they?ll have to make these switches exhaustive with a default clause or > other total pattern. When compiling a switch over such a sealed type, the > compiler can provide a useful error message (?I know this is a sealed type, but > I can?t provide full exhaustiveness checking here because you can?t see all the > subtypes, so you still need a default.?) > Javadoc. The list of permitted subtypes should be incorporated into the Javadoc. > Note that this is not exactly the same as the current ?All implementing > classes? list that Javadoc currently includes, so a list like ?All permitted > subtypes? might be added (possibly with some indication if the subtype is less > accessible than the parent, or including an annotation that there exist others > that are not listed.) > Open question: With the advent of records, which allow us to define classes in a > single line, the ?one class per file? rule starts to seem both a little silly, > and constrain the user?s ability to put related definitions together (which may > be more readable) while exporting a flat namespace in the public API. I think > it is worth considering relaxing this rule to permit for sealed classes, say: > allowing public auxilliary subtypes of the primary type, if the primary type is > public and sealed. i think we should relax the rule for any hierarchy not only the sealed one. R?mi From brian.goetz at oracle.com Fri Dec 7 22:33:24 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 7 Dec 2018 17:33:24 -0500 Subject: Sealed types In-Reply-To: <1413705338.44635.1544211749780.JavaMail.zimbra@u-pem.fr> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <1413705338.44635.1544211749780.JavaMail.zimbra@u-pem.fr> Message-ID: > basically forcing me to write the code that the compiler generates for > me when i use an anonymous class. Yes, and I'm OK with that :) You could look at the glass as 90% full (after all, you can still do what you want to do), or 10% empty (you had to be more explicit about it.)? I think its more like 90% full. And "explicit" is the name of the game here.? If I asked 1000 Java users what would go wrong if there were non-denotable subtypes, how many of them do you think would immediately say "That would be bad for my clients, because I'm depriving them of exhaustive decomposition?"? Any?? I doubt it.? Which means a lot of people will make the mistake of doing so without even realizing what they're doing.? By being explicit about what the subtypes are, it is harder to make this mistake.? That seems a reasonable tradeoff for the increased type checking. This "hosing the clients without realizing it" is a lot like this one: https://stackoverflow.com/questions/23453287/why-is-final-not-allowed-in-java-8-interface-methods/23476994#23476994 People wrote angry blogs about how broken Java 8 interface methods were because they didn't support all the modifiers that class methods did.? They would self-righteously claim "If I make a method final, you should assume I have a damn good reason, and not second-guess me."? It did not occur to one person that, if you give people the ability to make interface methods final, you might put your clients in a terrible (possibly impossible) situation.? When people are writing APIs, they rarely think enough about how this interacts with other APIs, or client code; they're focused through the lens of their own API.? Supporting anonymous or lambda subtypes makes it way too easy to forget that you're taking something important away from your clients. Now, you might think this feature is 95% about subclass control and 5% about exhaustive decomposition, and so this seems like the tail wagging the dog to you.? But it's an opportunity for use to do better type checking -- which makes programs more reliable -- and I don't want to give that up so easily. (And, if we're wrong now, we can always relax this later, once people are more educated about exhaustiveness.? Not so much in the other direction.) From maurizio.cimadamore at oracle.com Fri Dec 7 23:57:58 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Fri, 7 Dec 2018 23:57:58 +0000 Subject: enhanced enums - back from the dead? In-Reply-To: <2092712408.1146020.1544178384809.JavaMail.zimbra@u-pem.fr> References: <1970970547.807832.1544046196415.JavaMail.zimbra@u-pem.fr> <4474050b-cfca-d40b-3712-26f8109a8456@oracle.com> <1615585555.1032246.1544115212153.JavaMail.zimbra@u-pem.fr> <2092712408.1146020.1544178384809.JavaMail.zimbra@u-pem.fr> Message-ID: <3a831534-107d-353c-1979-906540e6a60f@oracle.com> > so let's retry > > public enum Foo> { > S(""), I(42); // JEP 301 mentions the diamond syntax > > private T t; > > public Foo(T t) { > this.t = t; > } > > T t() { > return t; > } > > public static void main(String[] args) { > Arrays.stream(values()).sorted(Comparator.comparing(Foo::t)).forEach(System.out::println); > } > } > > My original point was, when you introduce a raw type in a Stream, you end up with a warning on every methods because of the inference. > While if you use the correct type, with an unbounded wildcard, the compiler stop you to do stupid things, > basically, you are trading an error to a series of warning people will be happy to suppress with @SuppressWarnings. > > Arrays.stream(values()).sorted(Comparator.comparing(Foo::t)).forEach(System.out::println); > emits a series of warnings Yes, in this case the unchecked warning will hit. I understand that this is suboptimal (see at the end). > Stream.of(S, I).sorted(Comparator.comparing(Foo::t)).forEach(System.out::println); > emits an error Yes, this is no different than using classes, really. > BTW, it seems there is an issue somewhere in the compiler because > Arrays.stream((Foo[])values()).sorted(Comparator.comparing(Foo::t)).forEach(System.out::println); > happily compiles ?? Well, Foo and Foo are interchangeable, so I guess the compiler is also trading Foo[] for Foo[]. As per JLS 5.1.9: "There is an unchecked conversion from the raw array type G[]k to any array type of the form G[]k. (The notation []k indicates an array type of k dimensions.) Use of an unchecked conversion causes a compile-time unchecked warning unless all type arguments Ti (1 ? i ? n) are unbounded wildcards (?4.5.1), or the warning is suppressed by @SuppressWarnings (?9.6.4.5). " > You can inspect any class even the private one by reflection, but you can not call the constructor if the class is private, if the class is package private, the constructor is package private too so you are widening the access. As for access/security, we already check in many places that no call to the constructor of an enum can occur, so this doesn't seem like a new issue? I mean, we have to protect anyway against people reflectively calling a package-private constructor of the enum class; maybe this will require few more checks for the enum class subclasses, but what I'm saying is that there's already a check in there. > > It's not that i don't like the feature, it's that for me it's a feature you can not even put in the box of the features that we could do. We start with "hey we could do this !" but there are some typing issues. Now, what your are saying is that we can use raw types to not have the typing issues, but as i said above, you are trading an error to a bunch of warnings, doesn't seems to be a good deal*. I agree that having too many warnings is bad - in my experiment, although I touched a lot of code, including stream chains, I did not find them; Comparator.comparing is probably one of the worst beast (and doesn't work well with target typing even beside generic enums). Not sure if that shifts the balance one way or another, but point taken. On this topic, since I was there, I tried to tweak the prototype so that Enum.values() and Enum.valueOf() return wildcards Foo, but supertype is Enum and this seem to work surprisingly well, both in the tests I had and in the new one you suggest. Maybe that would minimize the raw type usage, pushing it quite behind the curtains, and strictly as a migration aid for APIs such as EnumSet/Map ? Maurizio >>>> Maurizio >>> R?mi >>> >>>>> public class LineParsing { >>>>> private final HashMap>> actionMap = >>>>> new HashMap<>(); >>>>> >>>>> public LineParsing with(String option, Consumer> >>>>> action) { >>>>> actionMap.put(option, action); >>>>> return this; >>>>> } >>>>> >>>>> public void parse(List args) { >>>>> var it = args.iterator(); >>>>> while(it.hasNext()) { >>>>> actionMap.get(it.next()).accept(it); >>>>> } >>>>> } >>>>> >>>>> public static void main(String[] args) { >>>>> var bean = new Object() { >>>>> Path input = Path.of("input.txt"); >>>>> boolean all = false; >>>>> }; >>>>> >>>>> new LineParsing() >>>>> .with("-input", it -> bean.input = Path.of(it.next())) >>>>> .with("-all", it -> bean.all = true) >>>>> .parse(List.of(args)); >>>>> } >>>>> } >>>>> >>>>> regards, >>>>> R?mi >>>>> >>>>> >>>>> ----- Mail original ----- >>>>>> De: "Maurizio Cimadamore" >>>>>> ?: "amber-spec-experts" >>>>>> Envoy?: Mercredi 5 D?cembre 2018 17:14:59 >>>>>> Objet: enhanced enums - back from the dead? >>>>>> Hi, >>>>>> as mentioned in [1], the work on enhanced enum stopped while ago as we >>>>>> have found some interoperability issues between generic enums and >>>>>> standard enum APIs such as EnumSet/EnumMap. >>>>>> >>>>>> Recently, we have discussed a possible approach that might get us out of >>>>>> the woods, which is described in greater details here: >>>>>> >>>>>> http://cr.openjdk.java.net/~mcimadamore/amber/enhanced-enums.html >>>>>> >>>>>> We have done some internal testing to convince ourselves that, from an >>>>>> operational perspective, where we end up is indeed good. Some external >>>>>> validation might also be very helpful, which is why we're also in the >>>>>> process of releasing the internal patch we have tested internally in the >>>>>> 'enhanced-enums' amber branch (we'll need to polish it a little :-)). >>>>>> >>>>>> Assuming that, usability-wise, our story ticks all the boxes, I think it >>>>>> might be worth discussing a few points: >>>>>> >>>>>> * Do we still like the features described in JEP 301, from an >>>>>> expressiveness point of view? >>>>>> >>>>>> * Both features described in JEP 301 require some sort of massaging. On >>>>>> the one hand sharper typing of enum constants has to take care of binary >>>>>> compatibility of enum constant subclasses into account (for this reason >>>>>> we redefine accessibility of said subclasses along with their binary >>>>>> names). On the other hand, with the newly proposed approach, generic >>>>>> enums also need some language aid (treatment of raw enum constants >>>>>> supertypes). Do we feel that the steps needed in order to accommodate >>>>>> these sharp edges are worth the increase in expressive power delivered >>>>>> by JEP 301? >>>>>> >>>>>> * Our proposed treatment for generic enums raises an additional, more >>>>>> philosophical, question: what are raw types *for* and how happy are we >>>>>> in seeing more of them (in the form of raw enum types)? >>>>>> >>>>>> Cheers >>>>>> Maurizio >>>>>> >>>>>> [1] - >>>>>> http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html From forax at univ-mlv.fr Sat Dec 8 12:45:38 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sat, 8 Dec 2018 13:45:38 +0100 (CET) Subject: enhanced enums - back from the dead? In-Reply-To: <3a831534-107d-353c-1979-906540e6a60f@oracle.com> References: <1970970547.807832.1544046196415.JavaMail.zimbra@u-pem.fr> <4474050b-cfca-d40b-3712-26f8109a8456@oracle.com> <1615585555.1032246.1544115212153.JavaMail.zimbra@u-pem.fr> <2092712408.1146020.1544178384809.JavaMail.zimbra@u-pem.fr> <3a831534-107d-353c-1979-906540e6a60f@oracle.com> Message-ID: <748617869.84460.1544273138942.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Maurizio Cimadamore" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Samedi 8 D?cembre 2018 00:57:58 > Objet: Re: enhanced enums - back from the dead? [...] >> >> It's not that i don't like the feature, it's that for me it's a feature you can >> not even put in the box of the features that we could do. We start with "hey we >> could do this !" but there are some typing issues. Now, what your are saying is >> that we can use raw types to not have the typing issues, but as i said above, >> you are trading an error to a bunch of warnings, doesn't seems to be a good >> deal*. > > I agree that having too many warnings is bad - in my experiment, > although I touched a lot of code, including stream chains, I did not > find them; Comparator.comparing is probably one of the worst beast (and > doesn't work well with target typing even beside generic enums). Not > sure if that shifts the balance one way or another, but point taken. > > On this topic, since I was there, I tried to tweak the prototype so that > Enum.values() and Enum.valueOf() return wildcards Foo, but supertype > is Enum and this seem to work surprisingly well, both in the tests > I had and in the new one you suggest. Maybe that would minimize the raw > type usage, pushing it quite behind the curtains, and strictly as a > migration aid for APIs such as EnumSet/Map ? Using Enum> should also work ? no ? > > Maurizio > R?mi From forax at univ-mlv.fr Sat Dec 8 17:27:22 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sat, 8 Dec 2018 18:27:22 +0100 (CET) Subject: Sealed types In-Reply-To: References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <1413705338.44635.1544211749780.JavaMail.zimbra@u-pem.fr> Message-ID: <1656531791.117955.1544290042352.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Vendredi 7 D?cembre 2018 23:33:24 > Objet: Re: Sealed types >> basically forcing me to write the code that the compiler generates for me when i >> use an anonymous class. > Yes, and I'm OK with that :) > You could look at the glass as 90% full (after all, you can still do what you > want to do), or 10% empty (you had to be more explicit about it.) I think its > more like 90% full. > And "explicit" is the name of the game here. If I asked 1000 Java users what > would go wrong if there were non-denotable subtypes, how many of them do you > think would immediately say "That would be bad for my clients, because I'm > depriving them of exhaustive decomposition?" Any? I doubt it. Which means a lot > of people will make the mistake of doing so without even realizing what they're > doing. By being explicit about what the subtypes are, it is harder to make this > mistake. That seems a reasonable tradeoff for the increased type checking. > This "hosing the clients without realizing it" is a lot like this one: > [ > https://stackoverflow.com/questions/23453287/why-is-final-not-allowed-in-java-8-interface-methods/23476994#23476994 > | > https://stackoverflow.com/questions/23453287/why-is-final-not-allowed-in-java-8-interface-methods/23476994#23476994 > ] > People wrote angry blogs about how broken Java 8 interface methods were because > they didn't support all the modifiers that class methods did. They would > self-righteously claim "If I make a method final, you should assume I have a > damn good reason, and not second-guess me." It did not occur to one person > that, if you give people the ability to make interface methods final, you might > put your clients in a terrible (possibly impossible) situation. When people are > writing APIs, they rarely think enough about how this interacts with other > APIs, or client code; they're focused through the lens of their own API. > Supporting anonymous or lambda subtypes makes it way too easy to forget that > you're taking something important away from your clients. > Now, you might think this feature is 95% about subclass control and 5% about > exhaustive decomposition, and so this seems like the tail wagging the dog to > you. But it's an opportunity for use to do better type checking -- which makes > programs more reliable -- and I don't want to give that up so easily. > (And, if we're wrong now, we can always relax this later, once people are more > educated about exhaustiveness. Not so much in the other direction.) Your argument about "hosing the clients without realizing it" already apply to sealed types. We are about to see angry blogs anyway, people saying that they can not add an implementation of a sealed interface thus sealed interfaces are a bad idea. With a sealed interface, the developer of the API claims the right to decide what are the subtypes to the detriment of the users of that interface. About the exhaustiveness, making a class private is enough for making the exhaustiveness to go away. I agree with you that people may shoot themselves in the foot too easily by creating a sealed hierarchy that is not exhaustive for the compiler, but the right thing is not to force subclasses to have a name but to add an annotation that verify that the exhaustiveness is guaranteed if the sealed interface is visible (subclasses has to be named *and* has to be as visible as the sealed interface) exactly like we have @FunctionalInterface. Obviously, we can have an annotation in one direction (i want exhaustiveness) or the other (i don't want exhaustiveness). R?mi From brian.goetz at oracle.com Sat Dec 8 18:49:06 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Sat, 8 Dec 2018 13:49:06 -0500 Subject: Sealed types In-Reply-To: <1656531791.117955.1544290042352.JavaMail.zimbra@u-pem.fr> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <1413705338.44635.1544211749780.JavaMail.zimbra@u-pem.fr> <1656531791.117955.1544290042352.JavaMail.zimbra@u-pem.fr> Message-ID: <3231250E-FBD1-433B-9AF1-006B40D73CD6@oracle.com> You are right, either way would be justified. I get why you like it this way; that?s a valid opinion. But, be careful of the ?just add a switch to make it go the other way? argument; while that certainly seems like it might ?make everyone happy? in that everyone can express what they want, what it really does is make the language more complicated for everyone, because now there are more things that everyone has to learn. And it seems pretty likely the ?return on incremental complexity? here is negative. So we should go one way or the other. Again, I get why you want to go the way you do. And it?s a valid preference; I think (at least right now) I would still prefer to set the balance of this feature more in favor of the clients. From dl at cs.oswego.edu Sun Dec 9 13:16:47 2018 From: dl at cs.oswego.edu (Doug Lea) Date: Sun, 9 Dec 2018 08:16:47 -0500 Subject: Sealed types In-Reply-To: <369e9889-6fd2-d605-d5fa-520ad5ff4b41@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <4153d8b5-16a6-6be8-0c6b-bbc2319d220b@cs.oswego.edu> <369e9889-6fd2-d605-d5fa-520ad5ff4b41@oracle.com> Message-ID: <723715c9-012c-4859-4622-b29c53e7f1de@cs.oswego.edu> On 12/7/18 1:43 PM, Brian Goetz wrote: >> Maybe "permits" -> "exclusivelyIncludes" or just "exclusively"? These >> seem less likely to be misinterpreted and also less likely to lead to >> any confusion with not-rare use of "permits" as a variable (in >> semaphores etc). > > Good thought. The main thought is to clarify that these are exclusive/complete types, as used in UML etc requirements. There are also some syntax holes that don't even need reserved words. Colon+parenthesized list seems OK: final interface Node : (A, B, C) { ... } ... and would allow even more compact declaration if the list elements could be either names or full definitions: final interface Node : ( record IntNode(int value), record PlusNode(Node left, Node right), record NegNode(Node node) ) { public void evaluate(); } -Doug From forax at univ-mlv.fr Sun Dec 9 13:28:29 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Sun, 9 Dec 2018 14:28:29 +0100 (CET) Subject: Sealed types In-Reply-To: <723715c9-012c-4859-4622-b29c53e7f1de@cs.oswego.edu> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <4153d8b5-16a6-6be8-0c6b-bbc2319d220b@cs.oswego.edu> <369e9889-6fd2-d605-d5fa-520ad5ff4b41@oracle.com> <723715c9-012c-4859-4622-b29c53e7f1de@cs.oswego.edu> Message-ID: <491616696.150499.1544362109404.JavaMail.zimbra@u-pem.fr> Hi Doug, using colon ':' will cause trouble to people that discover Java after having used C or C# given that ':' is used to introduce supertypes in those languages. Your proposed compact declaration is a mess visually if you declare a subtype which is a sealed interface with a list of subtypes, because it becomes a tree. R?mi ----- Mail original ----- > De: "Doug Lea"
> ?: "Brian Goetz" , "amber-spec-experts" > Envoy?: Dimanche 9 D?cembre 2018 14:16:47 > Objet: Re: Sealed types > On 12/7/18 1:43 PM, Brian Goetz wrote: >>> Maybe "permits" -> "exclusivelyIncludes" or just "exclusively"? These >>> seem less likely to be misinterpreted and also less likely to lead to >>> any confusion with not-rare use of "permits" as a variable (in >>> semaphores etc). >> >> Good thought. > > The main thought is to clarify that these are exclusive/complete types, > as used in UML etc requirements. > > There are also some syntax holes that don't even need reserved words. > Colon+parenthesized list seems OK: > > final interface Node : (A, B, C) { ... } > > ... and would allow even more compact declaration if the list elements > could be either names or full definitions: > > final interface Node : ( > record IntNode(int value), > record PlusNode(Node left, Node right), > record NegNode(Node node) > ) { > public void evaluate(); > } > > -Doug From dl at cs.oswego.edu Sun Dec 9 13:36:08 2018 From: dl at cs.oswego.edu (Doug Lea) Date: Sun, 9 Dec 2018 08:36:08 -0500 Subject: Sealed types In-Reply-To: <491616696.150499.1544362109404.JavaMail.zimbra@u-pem.fr> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <4153d8b5-16a6-6be8-0c6b-bbc2319d220b@cs.oswego.edu> <369e9889-6fd2-d605-d5fa-520ad5ff4b41@oracle.com> <723715c9-012c-4859-4622-b29c53e7f1de@cs.oswego.edu> <491616696.150499.1544362109404.JavaMail.zimbra@u-pem.fr> Message-ID: On 12/9/18 8:28 AM, Remi Forax wrote: > Hi Doug, > using colon ':' will cause trouble to people that discover Java after having used C or C# given that ':' is used to introduce supertypes in those languages. I just noted that ":" is available. So are others with "" prefix: ":>", "::", etc that would avoid this. > > Your proposed compact declaration is a mess visually if you declare a subtype which is a sealed interface with a list of subtypes, because it becomes a tree. > That's why it would be better to allow either names or full definitions. -Doug > R?mi > > ----- Mail original ----- >> De: "Doug Lea"
>> ?: "Brian Goetz" , "amber-spec-experts" >> Envoy?: Dimanche 9 D?cembre 2018 14:16:47 >> Objet: Re: Sealed types > >> On 12/7/18 1:43 PM, Brian Goetz wrote: >>>> Maybe "permits" -> "exclusivelyIncludes" or just "exclusively"? These >>>> seem less likely to be misinterpreted and also less likely to lead to >>>> any confusion with not-rare use of "permits" as a variable (in >>>> semaphores etc). >>> >>> Good thought. >> >> The main thought is to clarify that these are exclusive/complete types, >> as used in UML etc requirements. >> >> There are also some syntax holes that don't even need reserved words. >> Colon+parenthesized list seems OK: >> >> final interface Node : (A, B, C) { ... } >> >> ... and would allow even more compact declaration if the list elements >> could be either names or full definitions: >> >> final interface Node : ( >> record IntNode(int value), >> record PlusNode(Node left, Node right), >> record NegNode(Node node) >> ) { >> public void evaluate(); >> } >> >> -Doug > From brian.goetz at oracle.com Sun Dec 9 15:13:12 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Sun, 9 Dec 2018 10:13:12 -0500 Subject: Sealed types In-Reply-To: References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <1413705338.44635.1544211749780.JavaMail.zimbra@u-pem.fr> Message-ID: <039B4AB7-C716-46A6-9FE4-5C2C3D6A880C@oracle.com> I thought about Remi?s argument a little more: > >> basically forcing me to write the code that the compiler generates for me when i use an anonymous class. > And, it illustrates the danger of permitting syntactic shorthands out of convenience ? the user will not only not understand that the shorthand is merely a convenience, but will develop a sense of entitlement in it, and wonder why it won?t work everywhere. Sealed types are SUMs; a sum should declare what it is a sum of. If we just had the explicit syntax: sum X = { A, B, C }; No one would be too bent out of shape that a sum can?t (directly) contain an anonymous class or lambda, because OBVIOUSLY a sum has to name its parts. But, because of two quirks of syntax, Remi (and millions of others, I?m sure) now think I?m being gratuitously mean. Quirk 1 is attaching the sum to the superclass being declared. This is natural, in that we?re explicitly declaring it as a sum. Quirk 2 is the shorthand form, where we are willing to infer the subtypes when it is ?obvious?, because we want to be nice. (Shame that our reward for being nice is that people think we?re mean.) If we think this is a problem, perhaps we should drop the short-form of ?permits? (though I think that would merely make people think we?re mean in another way.) Or choose a short form that doesn?t seem to play into all-too-easy misconceptions of what the feature is for. (Certainly, because we can?t even come up with too good a short form right now, we should probably leave it out for now, until we find something that doesn?t require three drinks to seem sensible.) At root, though, this disagreement over restrictions is really a disagreement over what sealed types are. I want to say they are sums; Remi wants to say they are really just access control lists for class extension. I think the sum interpretation is more powerful and more grounded, so I lean that way. From forax at univ-mlv.fr Sun Dec 9 17:14:00 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sun, 9 Dec 2018 18:14:00 +0100 (CET) Subject: Sealed types In-Reply-To: <039B4AB7-C716-46A6-9FE4-5C2C3D6A880C@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <1413705338.44635.1544211749780.JavaMail.zimbra@u-pem.fr> <039B4AB7-C716-46A6-9FE4-5C2C3D6A880C@oracle.com> Message-ID: <1034420573.164864.1544375640572.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Dimanche 9 D?cembre 2018 16:13:12 > Objet: Re: Sealed types > I thought about Remi?s argument a little more: >>> basically forcing me to write the code that the compiler generates for me when i >>> use an anonymous class. > And, it illustrates the danger of permitting syntactic shorthands out of > convenience ? the user will not only not understand that the shorthand is > merely a convenience, but will develop a sense of entitlement in it, and wonder > why it won?t work everywhere. > Sealed types are SUMs; a sum should declare what it is a sum of. If we just had > the explicit syntax: > sum X = { A, B, C }; > No one would be too bent out of shape that a sum can?t (directly) contain an > anonymous class or lambda, because OBVIOUSLY a sum has to name its parts. But, > because of two quirks of syntax, Remi (and millions of others, I?m sure) now > think I?m being gratuitously mean. > Quirk 1 is attaching the sum to the superclass being declared. This is natural, > in that we?re explicitly declaring it as a sum. Quirk 2 is the shorthand form, > where we are willing to infer the subtypes when it is ?obvious?, because we > want to be nice. (Shame that our reward for being nice is that people think > we?re mean.) > If we think this is a problem, perhaps we should drop the short-form of > ?permits? (though I think that would merely make people think we?re mean in > another way.) Or choose a short form that doesn?t seem to play into > all-too-easy misconceptions of what the feature is for. (Certainly, because we > can?t even come up with too good a short form right now, we should probably > leave it out for now, until we find something that doesn?t require three drinks > to seem sensible.) > At root, though, this disagreement over restrictions is really a disagreement > over what sealed types are. I want to say they are sums; Remi wants to say they > are really just access control lists for class extension. I think the sum > interpretation is more powerful and more grounded, so I lean that way. We want to introduce sum types in Java. So we have to find a syntax, a runtime representation and it has to work well with the current features of Java. We can introduce them as sealed interface (note: interface not type), that one solution, in that case, because this feature has to be integrated with the rest of Java, i think we have to support anonymous class and lambda (given that if we delay the full support of this feature by the VM, the support of lambdas has to be delayed too because the lambda proxy class is only known at runtime). Another solution is to introduce a "real" sum type, in that case the runtime implementation do not have to use an interface, the sum type can be a new kind of class (sum class) supported by the VM and we can come with a syntax close to what we see in other existing languages that define sum types. sum X { A(int x, int y) | B(String s) { ... } | AnotherSumType | AnInterface | AClass | AnEnum | ARecord } In that case, you can not "implements" a sum type and the type defined inside as to have at least the same visibility as the sum type. The main issues with this solution is that it requires to introduce a new kind of type in the VM (if not erased) and it's harder to retrofit an existing hierarchy to be a sum type. So either we go with an interface and in that can i think we should support all ways to implement an interface or we go with a sum class and in that case i agree that we do not need to support anonymous classes. R?mi From brian.goetz at oracle.com Sun Dec 9 18:04:40 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Sun, 9 Dec 2018 13:04:40 -0500 Subject: Sealed types In-Reply-To: <1034420573.164864.1544375640572.JavaMail.zimbra@u-pem.fr> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <1413705338.44635.1544211749780.JavaMail.zimbra@u-pem.fr> <039B4AB7-C716-46A6-9FE4-5C2C3D6A880C@oracle.com> <1034420573.164864.1544375640572.JavaMail.zimbra@u-pem.fr> Message-ID: > > We can introduce them as sealed interface (note: interface not type) This is a new claim. Why is it that abstract classes can?t be sealed as well? (There is less _need_ for this, as abstract classes can use access control on their constructors to simulate sealing, but that?s a weak approximation, and doesn?t move us towards sums at all. > So either we go with an interface and in that can i think we should support all ways to implement an interface or we go with a sum class and in that case i agree that we do not need to support anonymous classes. I think what you?re really saying is: the syntax we pick will put ideas in people?s heads, and we should be wary if the most likely ideas it plants are not the ones we have in mind. Yes, that?s true. But it doesn?t remotely rise to the level of ?forced move? that you seem to be implying. So rather than pounding the table and saying ?it has to be this way?, can we try to frame the issue more as a mismatch between the candidate syntax and what we expect the user model to be, and work it from that direction? I don?t think I have to say it out loud, but just to be sure: the syntax should not drive the model, it should go the other way. If the syntax we?ve chosen is a poor fit for the model, first let?s talk about changing the syntax. From forax at univ-mlv.fr Sun Dec 9 19:14:28 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sun, 9 Dec 2018 20:14:28 +0100 (CET) Subject: Sealed types In-Reply-To: References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <57012882-a022-3da3-d894-b0fc347f4468@oracle.com> <1413705338.44635.1544211749780.JavaMail.zimbra@u-pem.fr> <039B4AB7-C716-46A6-9FE4-5C2C3D6A880C@oracle.com> <1034420573.164864.1544375640572.JavaMail.zimbra@u-pem.fr> Message-ID: <684756612.177593.1544382868354.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Dimanche 9 D?cembre 2018 19:04:40 > Objet: Re: Sealed types >> We can introduce them as sealed interface (note: interface not type) > This is a new claim. Why is it that abstract classes can?t be sealed as well? > (There is less _need_ for this, as abstract classes can use access control on > their constructors to simulate sealing, but that?s a weak approximation, and > doesn?t move us towards sums at all. Abstract class as sum type can be problematic if the subclass has itself a super class (see below). >> So either we go with an interface and in that can i think we should support all >> ways to implement an interface or we go with a sum class and in that case i >> agree that we do not need to support anonymous classes. > I think what you?re really saying is: the syntax we pick will put ideas in > people?s heads, and we should be wary if the most likely ideas it plants are > not the ones we have in mind. Yes, that?s true. But it doesn?t remotely rise to > the level of ?forced move? that you seem to be implying. We disagree about the "forced move". But that's the last line of my email, i think the issue i'm taking about before that last sentence is more important. So it's not about the syntax, it's about the user model and the runtime model. The way to define a sum type is: - to list all its component - have the subtypes to say that they are part of the sum type. If there is no way to implement a sum type i.e. to write 'implements' X in the subclass, we do not have to ask ourselves if we have to support anonymous classes The runtime model; - is it an interface ? - Is it a new kind of type ? To illustrate the difference between an interface or a new kind of type, if we have: sum X { A | X2 } sum X2 { B | C } if X is an interface, switch(X) requires case A and case X2, if it's a new kind of type, switch(X) requires case A, case B and case C. > So rather than pounding the table and saying ?it has to be this way?, can we try > to frame the issue more as a mismatch between the candidate syntax and what we > expect the user model to be, and work it from that direction? again, last sentence, i should have removed it, and yes, see above. > I don?t think I have to say it out loud, but just to be sure: the syntax should > not drive the model, it should go the other way. If the syntax we?ve chosen is > a poor fit for the model, first let?s talk about changing the syntax. I write a syntax down the same way you have given one, as an example. R?mi From maurizio.cimadamore at oracle.com Mon Dec 10 15:38:02 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 10 Dec 2018 15:38:02 +0000 Subject: enhanced enums - back from the dead? In-Reply-To: <748617869.84460.1544273138942.JavaMail.zimbra@u-pem.fr> References: <1970970547.807832.1544046196415.JavaMail.zimbra@u-pem.fr> <4474050b-cfca-d40b-3712-26f8109a8456@oracle.com> <1615585555.1032246.1544115212153.JavaMail.zimbra@u-pem.fr> <2092712408.1146020.1544178384809.JavaMail.zimbra@u-pem.fr> <3a831534-107d-353c-1979-906540e6a60f@oracle.com> <748617869.84460.1544273138942.JavaMail.zimbra@u-pem.fr> Message-ID: On 08/12/2018 12:45, forax at univ-mlv.fr wrote: > ----- Mail original ----- >> De: "Maurizio Cimadamore" >> ?: "Remi Forax" >> Cc: "amber-spec-experts" >> Envoy?: Samedi 8 D?cembre 2018 00:57:58 >> Objet: Re: enhanced enums - back from the dead? > [...] > >>> It's not that i don't like the feature, it's that for me it's a feature you can >>> not even put in the box of the features that we could do. We start with "hey we >>> could do this !" but there are some typing issues. Now, what your are saying is >>> that we can use raw types to not have the typing issues, but as i said above, >>> you are trading an error to a bunch of warnings, doesn't seems to be a good >>> deal*. >> I agree that having too many warnings is bad - in my experiment, >> although I touched a lot of code, including stream chains, I did not >> find them; Comparator.comparing is probably one of the worst beast (and >> doesn't work well with target typing even beside generic enums). Not >> sure if that shifts the balance one way or another, but point taken. >> >> On this topic, since I was there, I tried to tweak the prototype so that >> Enum.values() and Enum.valueOf() return wildcards Foo, but supertype >> is Enum and this seem to work surprisingly well, both in the tests >> I had and in the new one you suggest. Maybe that would minimize the raw >> type usage, pushing it quite behind the curtains, and strictly as a >> migration aid for APIs such as EnumSet/Map ? > Using Enum> should also work ? no ? No, we have tried that path and that doesn't work - ultimately you get an issue because EnumSet.of is accepting a Class, and, in case of a class literal, you get back a Class (Foo raw). So if supertype says Class> you get two incompatible constraints on T, namely Foo and Foo. Maurizio > >> Maurizio >> > R?mi From cushon at google.com Mon Dec 10 21:27:59 2018 From: cushon at google.com (Liam Miller-Cushon) Date: Mon, 10 Dec 2018 13:27:59 -0800 Subject: enhanced enums - back from the dead? In-Reply-To: References: Message-ID: I did some corpus analysis for the following question from your doc: There is a question on how broadly we want to affect typing rules for raw > types; one option is to alter the rules for all type references; another, > more conservative option, would be to use the refined rules only for enum > raw type references (on the basis that generic enums and raw types will be > frequently used together). The latter path has the clear advantage of > avoiding all kinds of source compatibility issues, but it is more > inconsistent - refactoring a generic enum into a class might lead to > surprises. I found roughly one breaking change per 55000 files. For context that's about four times as many as the fix that was discussed recently for JDK-8207224. Less quantitatively, in the corpus I looked at it is noticeable but wouldn't be very difficult to work around, especially because there's usually a trivial semantics-preserving fix of adding an explicit unchecked cast. (For the analysis I applied only the changes to Types from the enhanced-enums branch, and commented out all of the `isEnum` checks.) From brian.goetz at oracle.com Mon Dec 10 21:33:32 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 10 Dec 2018 16:33:32 -0500 Subject: enhanced enums - back from the dead? In-Reply-To: References: Message-ID: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> Thanks for the data! Can you clarify: is 1 out of 55Kfiles for the "all raw type refs" option, or the "only for raw enum refs"?? It looks like you are saying the former? On 12/10/2018 4:27 PM, Liam Miller-Cushon wrote: > I did some corpus analysis for the following question from your doc: > > There is a question on how broadly we want to affect typing rules > for raw types; one option is to alter the rules for all type > references; another, more conservative option, would be to use the > refined rules only for enum raw type references (on the basis that > generic enums and raw types will be frequently used together). The > latter path has the clear advantage of avoiding all kinds of > source compatibility issues, but it is more inconsistent - > refactoring a generic enum into a class might lead to surprises. > > > I found roughly one breaking change per 55000 files. > > For context that's about four times as many as the fix that was > discussed recently for JDK-8207224. Less quantitatively, in the corpus > I looked at it is noticeable but wouldn't be very difficult to work > around, especially because there's usually a trivial > semantics-preserving fix of adding an explicit unchecked cast. > > (For the analysis I applied only the changes to?Types from the > enhanced-enums branch, and commented out all of the `isEnum` checks.) From cushon at google.com Mon Dec 10 21:35:37 2018 From: cushon at google.com (Liam Miller-Cushon) Date: Mon, 10 Dec 2018 13:35:37 -0800 Subject: enhanced enums - back from the dead? In-Reply-To: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> Message-ID: On Mon, Dec 10, 2018 at 1:33 PM Brian Goetz wrote: > Can you clarify: is 1 out of 55Kfiles for the "all raw type refs" option, > or the "only for raw enum refs"? It looks like you are saying the former? > Yes: 1:55000 was for all raw type refs, with no special-casing of enums. (The other variation should be fully source compatible for corpuses that don't contain any generic enums, right?) From maurizio.cimadamore at oracle.com Mon Dec 10 23:48:48 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Mon, 10 Dec 2018 23:48:48 +0000 Subject: enhanced enums - back from the dead? In-Reply-To: References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> Message-ID: <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> Thanks for the data - I guess the takeaway is that, as expected, making type-checking of raw typing sharper will help migration (e.g. add type parameters to? existing code that might use some generic signatures already), but it comes at the cost of some source incompatibility. If we restrict this only to enums, of course this would be a non-issue; as you say, enums are non-generic right now, so we have a clean slate (assuming we're ok with the asymmetry). Thanks again. Maurizio On 10/12/2018 21:35, Liam Miller-Cushon wrote: > On Mon, Dec 10, 2018 at 1:33 PM Brian Goetz > wrote: > > Can you clarify: is 1 out of 55Kfiles for the "all raw type refs" > option, or the "only for raw enum refs"?? It looks like you are > saying the former? > > > Yes: 1:55000 was for all raw type refs, with no special-casing of enums. > > (The other variation should be fully source compatible for corpuses > that don't contain any generic enums, right?) From brian.goetz at oracle.com Mon Dec 10 23:58:59 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 10 Dec 2018 18:58:59 -0500 Subject: enhanced enums - back from the dead? In-Reply-To: <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> Message-ID: <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> > If we restrict this only to enums, of course this would be a non-issue; as you say, enums are non-generic right now, so we have a clean slate (assuming we're ok with the asymmetry). > There?s an even more conservative option: restrict this only to the type variable of Enum>, not just to the type variables of all supertypes of an enum. Then, the asymmetry is undetectable, in that it is illegal for a non-enum class to extend Enum, right? From maurizio.cimadamore at oracle.com Tue Dec 11 10:55:12 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Tue, 11 Dec 2018 10:55:12 +0000 Subject: enhanced enums - back from the dead? In-Reply-To: <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> Message-ID: <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> On 10/12/2018 23:58, Brian Goetz wrote: >> If we restrict this only to enums, of course this would be a non-issue; as you say, enums are non-generic right now, so we have a clean slate (assuming we're ok with the asymmetry). >> > > There?s an even more conservative option: restrict this only to the type variable of Enum>, not just to the type variables of all supertypes of an enum. Then, the asymmetry is undetectable, in that it is illegal for a non-enum class to extend Enum, right? > Yes, that would be the most conservative. If we took that path, I think a more direct way to describe/specify it, is to say that a raw enum reference (e.g. Foo) has a special direct supertype Enum and leave all other rules untouched. Maurizio From brian.goetz at oracle.com Tue Dec 11 14:02:44 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 11 Dec 2018 09:02:44 -0500 Subject: Fwd: Re: enhanced enums - back from the dead? In-Reply-To: References: Message-ID: <71159551-61ab-a1c0-0890-9a43e0032177@oracle.com> Received on the -comments list. -------- Forwarded Message -------- Subject: Re: enhanced enums - back from the dead? Date: Tue, 11 Dec 2018 11:13:52 +0000 From: elias vasylenko To: amber-spec-comments at openjdk.java.net So going back a little, the suggestion of R?mi is to have the direct supertype of Foo be Enum> instead of the proposed Enum? And the problem with this is that with invocation of e.g. noneOf or allOf with a class literal we have an assignment of Class to Class, which can't satisfy the bound E <: Enum? I hope I have understood so far. Then could this not be addressed by also adjusting the signature of EnumSet, and some of the methods on it (those which mention Class), such that the type parameters are specified as >? Then I think the bound is satisfied as follows: Foo <: [E:=Foo]Enum Foo <: Enum Enum> <: Enum Foo <: Foo And the only things that can satisfy the bound on E would be E:=Foo, E:=Foo, or a capture of E, or the infinite type Enum> This does seem to create some other problems. One problem (or rather, an avoidable pitfall) is that the now-legal aforementioned infinite type would describe an enum set which accepts enum values belonging to different classes, which means losing static type safety. But since the infinite type is not denotable we just have to make sure it can never be inferred anywhere, meaning that the signatures of e.g. the Enum.of methods should retain their existing type parameter bound of >, such that we can only infer E:=Foo. Another problem is that in any existing class which extends EnumSet some of the overriding method signatures may be made incompatible. I expect this would require refining the notion of override equivalence of signatures in the JLS to a notion of override compatibility, where a little flexibility is allowed in overriding methods to have more specific bounds on type parameters (so long as the erased signature is unchanged of course). I don't know if this is feasible, but I think there's an argument that it's a sensible refinement regardless of the enhanced enums issue. It would be nice to be able to adjust the bounds on the type parameters of a method to be less specific without worrying about breaking source compatibility. I'm sure there's a lot that I've overlooked, this is quite difficult to reason about in the abstract. Eli On Mon, 10 Dec 2018 at 15:38, Maurizio Cimadamore < maurizio.cimadamore at oracle.com> wrote: > On 08/12/2018 12:45, forax at univ-mlv.fr wrote: >> ----- Mail original ----- >>> De: "Maurizio Cimadamore" >>> ?: "Remi Forax" >>> Cc: "amber-spec-experts" >>> Envoy?: Samedi 8 D?cembre 2018 00:57:58 >>> Objet: Re: enhanced enums - back from the dead? >> [...] >> >>>> It's not that i don't like the feature, it's that for me it's a > feature you can >>>> not even put in the box of the features that we could do. We start > with "hey we >>>> could do this !" but there are some typing issues. Now, what your are > saying is >>>> that we can use raw types to not have the typing issues, but as i said > above, >>>> you are trading an error to a bunch of warnings, doesn't seems to be a > good >>>> deal*. >>> I agree that having too many warnings is bad - in my experiment, >>> although I touched a lot of code, including stream chains, I did not >>> find them; Comparator.comparing is probably one of the worst beast (and >>> doesn't work well with target typing even beside generic enums). Not >>> sure if that shifts the balance one way or another, but point taken. >>> >>> On this topic, since I was there, I tried to tweak the prototype so that >>> Enum.values() and Enum.valueOf() return wildcards Foo, but supertype >>> is Enum and this seem to work surprisingly well, both in the tests >>> I had and in the new one you suggest. Maybe that would minimize the raw >>> type usage, pushing it quite behind the curtains, and strictly as a >>> migration aid for APIs such as EnumSet/Map ? >> Using Enum> should also work ? no ? > > No, we have tried that path and that doesn't work - ultimately you get > an issue because EnumSet.of is accepting a Class, and, in case of a > class literal, you get back a Class (Foo raw). So if supertype says > Class> you get two incompatible constraints on T, namely Foo and > Foo. > > Maurizio > >>> Maurizio >>> >> R?mi > From maurizio.cimadamore at oracle.com Tue Dec 11 15:37:12 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Tue, 11 Dec 2018 15:37:12 +0000 Subject: Fwd: Re: enhanced enums - back from the dead? In-Reply-To: <71159551-61ab-a1c0-0890-9a43e0032177@oracle.com> References: <71159551-61ab-a1c0-0890-9a43e0032177@oracle.com> Message-ID: On 11/12/2018 14:02, Brian Goetz wrote: > Received on the -comments list. > > > -------- Forwarded Message -------- > Subject: Re: enhanced enums - back from the dead? > Date: Tue, 11 Dec 2018 11:13:52 +0000 > From: elias vasylenko > To: amber-spec-comments at openjdk.java.net > > > > So going back a little, the suggestion of R?mi is to have the direct > supertype of Foo be Enum> instead of the proposed Enum? > > And the problem with this is that with invocation of e.g. noneOf or allOf > with a class literal we have an assignment of Class to Class, > which > can't satisfy the bound E <: Enum? Yes, that is the issue > > I hope I have understood so far. Then could this not be addressed by also > adjusting the signature of EnumSet, and some of the methods on it (those > which mention Class), such that the type parameters are specified as extends Enum>? Then I think the bound is satisfied as > follows: Changing signature is one way, others have suggested to change typing of class literals to also use wildcards. I think either way we're in a world of migration pain. > > Foo <: [E:=Foo]Enum > Foo <: Enum > Enum> <: Enum > Foo <: Foo > > And the only things that can satisfy the bound on E would be E:=Foo, > E:=Foo, or a capture of E, or the infinite type Enum extends ...>> > > This does seem to create some other problems. > > One problem (or rather, an avoidable pitfall) is that the now-legal > aforementioned infinite type would describe an enum set which accepts enum > values belonging to different classes, which means losing static type > safety. But since the infinite type is not denotable we just have to make > sure it can never be inferred anywhere, meaning that the signatures of > e.g. > the Enum.of methods should retain their existing type parameter bound > of extends Enum>, such that we can only infer E:=Foo. Javac doesn't even support full blown infinite types, but truncates them at the second level of nesting, which will probably cause issues here. > > Another problem is that in any existing class which extends EnumSet > some of > the overriding method signatures may be made incompatible. I expect this > would require refining the notion of override equivalence of signatures in > the JLS to a notion of override compatibility, where a little flexibility > is allowed in overriding methods to have more specific bounds on type > parameters (so long as the erased signature is unchanged of course). > I don't know if this is feasible, but I think there's an argument that > it's > a sensible refinement regardless of the enhanced enums issue. It would be > nice to be able to adjust the bounds on the type parameters of a method to > be less specific without worrying about breaking source compatibility. Changing signatures (of the bounds of the Enum class, of the bounds of the enum-accepting generic methods) is something that has been considered a year ago - but we are afraid of the source compatibility impact that would be caused by this. As you mention, this will break at the very least overriding/hiding, and there are other more subtle issues (reflection will give you different generic types if you ask for bounds etc.). Also, I believe that _any_ code out there manipulating enum has effectively copied the EnumSet/EnumMap pattern, with strict fbounds; so while we could fix our JDK types, we could not fix all the other code with similar signatures. Which is what has sent us in a different direction. Thanks Maurizio > > I'm sure there's a lot that I've overlooked, this is quite difficult to > reason about in the abstract. > > Eli > > On Mon, 10 Dec 2018 at 15:38, Maurizio Cimadamore < > maurizio.cimadamore at oracle.com> wrote: > >> On 08/12/2018 12:45,forax at univ-mlv.fr wrote: >>> ----- Mail original ----- >>>> De: "Maurizio Cimadamore" >>>> ?: "Remi Forax" >>>> Cc: "amber-spec-experts" >>>> Envoy?: Samedi 8 D?cembre 2018 00:57:58 >>>> Objet: Re: enhanced enums - back from the dead? >>> [...] >>> >>>>> It's not that i don't like the feature, it's that for me it's a >> feature you can >>>>> not even put in the box of the features that we could do. We start >> with "hey we >>>>> could do this !" but there are some typing issues. Now, what your are >> saying is >>>>> that we can use raw types to not have the typing issues, but as i said >> above, >>>>> you are trading an error to a bunch of warnings, doesn't seems to be a >> good >>>>> deal*. >>>> I agree that having too many warnings is bad - in my experiment, >>>> although I touched a lot of code, including stream chains, I did not >>>> find them; Comparator.comparing is probably one of the worst beast (and >>>> doesn't work well with target typing even beside generic enums). Not >>>> sure if that shifts the balance one way or another, but point taken. >>>> >>>> On this topic, since I was there, I tried to tweak the prototype so that >>>> Enum.values() and Enum.valueOf() return wildcards Foo, but supertype >>>> is Enum and this seem to work surprisingly well, both in the tests >>>> I had and in the new one you suggest. Maybe that would minimize the raw >>>> type usage, pushing it quite behind the curtains, and strictly as a >>>> migration aid for APIs such as EnumSet/Map ? >>> Using Enum> should also work ? no ? >> >> No, we have tried that path and that doesn't work - ultimately you get >> an issue because EnumSet.of is accepting a Class, and, in case of a >> class literal, you get back a Class (Foo raw). So if supertype says >> Class> you get two incompatible constraints on T, namely Foo and >> Foo. >> >> Maurizio >> >>>> Maurizio >>>> >>> R?mi >> From brian.goetz at oracle.com Tue Dec 11 20:08:38 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 11 Dec 2018 15:08:38 -0500 Subject: Flow scoping Message-ID: (Note to observers: we did an internal survey so I could gauge the comfort level of the EG with the flow scoping rules for pattern binding variables.? We'll do a public survey at some point in the future.) Thanks for taking the time to take the quiz.? The results were pretty good.? For the early examples, which were mostly about if-then and short-circuiting booleans, people got the "right" answer (the answer the current rules, at http://cr.openjdk.java.net/~briangoetz/amber/pattern-semantics.html, gives): ??? if (x instanceof String v) { ??????? // x in scope here ??? } ??? else { ??????? // x not in scope here ??? } Note that we say "not in scope", not just "not DA".? There are a number of reasons for choosing this seemingly more complicated approach.? (These have been discussed here before; I'm mostly summarizing.)? One reason is the scoping for traditional "switch" gets in the way: ??? switch (x) { ??????? case String v: ...; break; ??????? case Integer v: ...; break; ??????? case Long v: ...; break; ??? } Since the body of a switch is one big scope, when we get to the second case, `v` would already be in scope, and have type `String`.? Similarly, in an if-else block that represents the same computation: ??? if (x instanceof String v) { ... } ??? else if (x instanceof Integer v) { ... } ??? else if (x instanceof Long v) { ... } (Note too, that it is an important design constraint that the rules allow the above two to be freely refactored into each other.) The flow-scoping -- where scoping of binding variables is _defined by_ the DA rules, means that where a binding variable is not sensible to use, it is actually _not in scope_, making it free for reuse. At first, this makes constructs like: ??? if (!(x instanceof String v) || v.length() == 0) { ??????? // v not in scope ??? } ??? else { ??????? // v in scope here ??? } kind of scary; the "shape" of v's scope is not contiguous.? But, I think its a product of a series of forced moves.? We want users to be able to freely refactor `if (x) s; else t` into `if (!x) t; else s`; if the language didn't allow that refactoring, that would be seriously smelly. The same is true with conditional expressions: `x ? e : f` should be equivalent to `!x ? f : e`. (Some of you are surely saying "but can't we just find an approximate enclosing rectangular scope, and use that?"? We tried, pretty hard.? Not only did it give us problems with the switch/if-else chain scoping, but it meant that variables could be in scope _before_ their declaration -- which is also pretty weird.) (Some of you are probably saying "can't we fix the switch/chain scoping by relaxing the shadowing rules, perhaps to allow shadowing of a DU variable?? This too, has its own complexities.) At least two of you spotted the trick question, which was #12: ??? if (x instanceof Comparable v && x instanceof Serializable v) { ... } Flow scoping has two touch points with DA/DU, and I left out the second from the instructions.? The first is that a binding variable is in scope iff it is DA at that point; the second is that a binding declaration is illegal if the variable is not DU at that point.? So the above is illegal because we don't know `v` is DU in the second binding. Before we get to the harder ones, can people share what made them uncomfortable, for the ones where you marked "not comfortable with the answer"? From brian.goetz at oracle.com Tue Dec 11 20:15:22 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 11 Dec 2018 15:15:22 -0500 Subject: enhanced enums - back from the dead? In-Reply-To: <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> Message-ID: This uber-conservative approach seems a pretty reasonable approach to me; after all, enums are a language feature, and Enum is a constrained class (can't implement it directly), so it is not unreasonable to define its typing wrt its supertypes specially. So, let's get back to Maurizio's original question, which is: At one point, we thought this feature was pretty cool, and invested some work in it.? Then we ran into a roadblock, and wrote it off.? Now, we've got a reasonable way to clear the roadblock. Which brings us back to: ?- Do we still like the features described in JEP 301? And, if we're not sure about that, what can we do to get more sure? >> There?s an even more conservative option: restrict this only to the >> type variable of Enum>, not just to the type >> variables of all supertypes of an enum.? Then, the asymmetry is >> undetectable, in that it is illegal for a non-enum class to extend >> Enum, right? >> > Yes, that would be the most conservative. If we took that path, I > think a more direct way to describe/specify it, is to say that a raw > enum reference (e.g. Foo) has a special direct supertype Enum and > leave all other rules untouched. > > Maurizio > From kevinb at google.com Wed Dec 12 16:40:17 2018 From: kevinb at google.com (Kevin Bourrillion) Date: Wed, 12 Dec 2018 08:40:17 -0800 Subject: enhanced enums - back from the dead? In-Reply-To: References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> Message-ID: On Tue, Dec 11, 2018 at 12:25 PM Brian Goetz wrote: This uber-conservative approach seems a pretty reasonable approach to > me; after all, enums are a language feature, and Enum is a > constrained class (can't implement it directly), so it is not > unreasonable to define its typing wrt its supertypes specially. > > So, let's get back to Maurizio's original question, which is: At one > point, we thought this feature was pretty cool, and invested some work > in it. Then we ran into a roadblock, and wrote it off. Now, we've got > a reasonable way to clear the roadblock. Which brings us back to: > > - Do we still like the features described in JEP 301? > What proportion of enum use cases benefit from this? Offhand, I would have to guess that it is less than 0.1% (and if it turned out to be *far* less it wouldn't *shock* me). Does anyone believe it's *likely enough* to be >0.1% that it's worth my effort to try to research that question in our codebase (which would take a bit of work)? If not, and we can stipulate that the need is rare, this means it will be a very special tool for very special people; a dark corner in the language feature set that most Java developers will never have need to know -- until they suddenly encounter code that uses it, upon which they need to invest an amount of effort understanding what is going on, even though they may have 14 years of believing they understood enums under their belts already. And if the need is this rare, this effort they put in might never be fully paid back. On the flip side, for a developer who does have this use case, and could benefit from the feature, what are the chances that developer will even know about it? It may be so special-purpose that we have no real reason to assume they'll know what to do. On top of this, it seems the feature apparently has a blast radius onto aspects of enum design that have previously been stable. Can a 0.1% use case ever really be worth this? -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com From brian.goetz at oracle.com Wed Dec 12 17:01:06 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 12 Dec 2018 12:01:06 -0500 Subject: enhanced enums - back from the dead? In-Reply-To: References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> Message-ID: <0803D1BE-38A2-4EC0-88E4-26F7B9BC2342@oracle.com> Yes, you could spin this feature as ?its sort of for experts?, note the incremental complexity, and then raise the ?why bother? flag. That?s a valid question. But, I prefer to look at this feature in a different way. This is not, IMO, just ?let?s make enums do more.? This is about _simplifying_ the language model by removing gratuitous interactions between features. Enums are a constrained form of classes, one whose instances are singletons managed by the runtime. Because the user gives up instance control, we?re able to reward the user with things like equals/hashCode/toString, serialization, etc. That?s a good trade. Enums can still use most of the things that classes have ? fields, methods, constructors, interfaces. But there?s no reason they can?t be generic, and allowing that would reduce the inessential differences between enums and classes. The other asymmetry is newer; since we inferred sharp types for anonymous classes when we did LVTI, inferring a weaker type for enum constants is now an asymmetry (one we were aware of when we did LVTI, but the plan all along was to align these.) I know that I personally have run into both of these limitations at least once in every large project I?ve ever done. You start out with an enum, for good reasons, then you hit the limits, and then have to refactor to classes, manually inflating the instance control boilerplate. Its frustrating, in part, because its unnecessary. There?s nothing inconsistent about generic enums; it?s just an accidental ?pick one? constraint that you discover when you find yourself wanting both. So, I prefer to look at this feature as ?regularization? or ?removing gratuitous interactions?, rather than ?making enums more complicated.? (It?s in the same category as some other things we?re considering, such as allowing local methods, or refining the awful rules about static members in nested classes.) All of these are accidental sharp edges, that create unnecessary cognitive load for users to keep track of which features can be used in conjunction with which others. > On Dec 12, 2018, at 11:40 AM, Kevin Bourrillion wrote: > > On Tue, Dec 11, 2018 at 12:25 PM Brian Goetz > wrote: > > This uber-conservative approach seems a pretty reasonable approach to > me; after all, enums are a language feature, and Enum is a > constrained class (can't implement it directly), so it is not > unreasonable to define its typing wrt its supertypes specially. > > So, let's get back to Maurizio's original question, which is: At one > point, we thought this feature was pretty cool, and invested some work > in it. Then we ran into a roadblock, and wrote it off. Now, we've got > a reasonable way to clear the roadblock. Which brings us back to: > > - Do we still like the features described in JEP 301? > > What proportion of enum use cases benefit from this? Offhand, I would have to guess that it is less than 0.1% (and if it turned out to be far less it wouldn't shock me). Does anyone believe it's likely enough to be >0.1% that it's worth my effort to try to research that question in our codebase (which would take a bit of work)? > > If not, and we can stipulate that the need is rare, this means it will be a very special tool for very special people; a dark corner in the language feature set that most Java developers will never have need to know -- until they suddenly encounter code that uses it, upon which they need to invest an amount of effort understanding what is going on, even though they may have 14 years of believing they understood enums under their belts already. And if the need is this rare, this effort they put in might never be fully paid back. > > On the flip side, for a developer who does have this use case, and could benefit from the feature, what are the chances that developer will even know about it? It may be so special-purpose that we have no real reason to assume they'll know what to do. > > On top of this, it seems the feature apparently has a blast radius onto aspects of enum design that have previously been stable. > > Can a 0.1% use case ever really be worth this? > > -- > Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com From register.amber at brychcy.de Wed Dec 12 17:34:55 2018 From: register.amber at brychcy.de (Till Brychcy) Date: Wed, 12 Dec 2018 18:34:55 +0100 Subject: Sealed types Message-ID: <446C05C6-9BD1-44F5-B094-DD0926624B99@brychcy.de> Are new keywords really necessary for this? Given that |super| can be already be used as opposite of |extends| in wildcard bounds, it looks natural to me to use it instead of |final| and |permits|, so final interface Node permits A, B, C { ... } would become interface Node super A,B,C {...} As shortcut for nest mates, I could imagine something with * like in imports, e.g. interface Node super Node.* { ...} From kevinb at google.com Wed Dec 12 17:38:13 2018 From: kevinb at google.com (Kevin Bourrillion) Date: Wed, 12 Dec 2018 09:38:13 -0800 Subject: enhanced enums - back from the dead? In-Reply-To: <0803D1BE-38A2-4EC0-88E4-26F7B9BC2342@oracle.com> References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> <0803D1BE-38A2-4EC0-88E4-26F7B9BC2342@oracle.com> Message-ID: Thanks Brian, that helps. For example, the idea of switching on a compile-time constant has always been conceptually 100% orthogonal to the type of that constant, and it really will be simpler when one day we can just switch on anything. That's a nice simplification* regardless* of whether anyone even uses the new ability. Anyone who wanted to switch on a long would expect it to just work, would have zero confusion about what the code meant if they encountered it, etc. It becomes easily understood as something that *always should have* worked -- there were just random "holes" in old versions that eventually got filled in. It's the best case. Thanks to your explanation I see that this case is like that one in theory. It's not *quite *the same thing in practice. That is, I know that a person can both read and write "switch (someLong)" with no puzzlement, but I don't think the same is true here; enumness and genericness are *conceptually* orthogonal but most people can only come to that understanding through a bit of confusion first, and they can't necessarily guess the right syntax to use. Secondly, it's hard to imagine there being any unforeseen consequences of switching on a long. Here, I don't really know how to think it through yet. I would appreciate any "here's how we know that the changes we need to support this will have no user-visible consequences at all except for X, Y, and Z" type of explanation we have (apologies if I missed it). On Wed, Dec 12, 2018 at 9:01 AM Brian Goetz wrote: > Yes, you could spin this feature as ?its sort of for experts?, note the > incremental complexity, and then raise the ?why bother? flag. That?s a > valid question. > > But, I prefer to look at this feature in a different way. This is not, > IMO, just ?let?s make enums do more.? This is about _simplifying_ the > language model by removing gratuitous interactions between features. Enums > are a constrained form of classes, one whose instances are singletons > managed by the runtime. Because the user gives up instance control, we?re > able to reward the user with things like equals/hashCode/toString, > serialization, etc. That?s a good trade. Enums can still use most of the > things that classes have ? fields, methods, constructors, interfaces. But > there?s no reason they can?t be generic, and allowing that would reduce the > inessential differences between enums and classes. > > The other asymmetry is newer; since we inferred sharp types for anonymous > classes when we did LVTI, inferring a weaker type for enum constants is now > an asymmetry (one we were aware of when we did LVTI, but the plan all along > was to align these.) > > I know that I personally have run into both of these limitations at least > once in every large project I?ve ever done. You start out with an enum, > for good reasons, then you hit the limits, and then have to refactor to > classes, manually inflating the instance control boilerplate. Its > frustrating, in part, because its unnecessary. There?s nothing > inconsistent about generic enums; it?s just an accidental ?pick one? > constraint that you discover when you find yourself wanting both. > > So, I prefer to look at this feature as ?regularization? or ?removing > gratuitous interactions?, rather than ?making enums more complicated.? > (It?s in the same category as some other things we?re considering, such as > allowing local methods, or refining the awful rules about static members in > nested classes.) All of these are accidental sharp edges, that create > unnecessary cognitive load for users to keep track of which features can be > used in conjunction with which others. > > > On Dec 12, 2018, at 11:40 AM, Kevin Bourrillion wrote: > > On Tue, Dec 11, 2018 at 12:25 PM Brian Goetz > wrote: > > This uber-conservative approach seems a pretty reasonable approach to >> me; after all, enums are a language feature, and Enum is a >> constrained class (can't implement it directly), so it is not >> unreasonable to define its typing wrt its supertypes specially. >> >> So, let's get back to Maurizio's original question, which is: At one >> point, we thought this feature was pretty cool, and invested some work >> in it. Then we ran into a roadblock, and wrote it off. Now, we've got >> a reasonable way to clear the roadblock. Which brings us back to: >> >> - Do we still like the features described in JEP 301? >> > > What proportion of enum use cases benefit from this? Offhand, I would have > to guess that it is less than 0.1% (and if it turned out to be *far* less > it wouldn't *shock* me). Does anyone believe it's *likely enough* to be > >0.1% that it's worth my effort to try to research that question in our > codebase (which would take a bit of work)? > > If not, and we can stipulate that the need is rare, this means it will be > a very special tool for very special people; a dark corner in the language > feature set that most Java developers will never have need to know -- until > they suddenly encounter code that uses it, upon which they need to invest > an amount of effort understanding what is going on, even though they may > have 14 years of believing they understood enums under their belts already. > And if the need is this rare, this effort they put in might never be fully > paid back. > > On the flip side, for a developer who does have this use case, and could > benefit from the feature, what are the chances that developer will even > know about it? It may be so special-purpose that we have no real reason to > assume they'll know what to do. > > On top of this, it seems the feature apparently has a blast radius onto > aspects of enum design that have previously been stable. > > Can a 0.1% use case ever really be worth this? > > -- > Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com > > > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com From brian.goetz at oracle.com Wed Dec 12 17:57:19 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 12 Dec 2018 12:57:19 -0500 Subject: enhanced enums - back from the dead? In-Reply-To: References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> <0803D1BE-38A2-4EC0-88E4-26F7B9BC2342@oracle.com> Message-ID: <763BEFE6-F76C-4154-939C-FF0D1E37FD40@oracle.com> > Thanks to your explanation I see that this case is like that one in theory. It's not quite the same thing in practice. That is, I know that a person can both read and write "switch (someLong)" with no puzzlement, but I don't think the same is true here; enumness and genericness are conceptually orthogonal but most people can only come to that understanding through a bit of confusion first, and they can't necessarily guess the right syntax to use. So, there?s two lenses through which we can view ?language complexity?. The first is incremental complexity relative to an existing Java developer who?s already learned the old way; the second is relative to a _new_ developer learning the new Java vs the old Java. Some changes add to complexity in both senses; these are when we add a whole new feature. (Generics, lambdas, and modules are all in this category ? they may still be justified on the benefits, but the costs are indisputable.) This one, I believe, is more asymmetric; those who have invested effort in internalizing the gratuitous differences may have come to see them as normal, and now have to unlearn them, which takes more effort. (Though even then, they may come to a point where they say ?ah, that?s simpler.?) > Secondly, it's hard to imagine there being any unforeseen consequences of switching on a long. Here, I don't really know how to think it through yet. I would appreciate any "here's how we know that the changes we need to support this will have no user-visible consequences at all except for X, Y, and Z" type of explanation we have (apologies if I missed it). Yep, that?s a harder one. In this feature, there are two changes (that we know of so far) that might cause such observable effects. 1. Not erasing the type parameter of Enum when computing the super type of a raw enum type. 2. Changing the translation of enum constants. Right now, if you have an enum constant that has a body, it gets translated as an inner class, Foo$1. We would probably change that to a named nested class, such as Foo$A. For (1), we?re all scratching our heads to imagine a negative consequence, and not coming up with one, but there is the risk of ?proof by lack of imagination.? For (2), I think there?s more risk here; people could reflectively look up enum constants as Foo$23, and this will break, but ? is this in the category of ?ask a stupid question, get a stupid answer?? (Note that for serialized instances, we?re covered, as serialization represents enum constants by name.) I think these are the two things to worry about. From brian.goetz at oracle.com Wed Dec 12 17:58:53 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Wed, 12 Dec 2018 12:58:53 -0500 Subject: enhanced enums - back from the dead? In-Reply-To: References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> <0803D1BE-38A2-4EC0-88E4-26F7B9BC2342@oracle.com> Message-ID: > Secondly, it's hard to imagine there being any unforeseen consequences of switching on a long. Here, I don't really know how to think it through yet. I would appreciate any "here's how we know that the changes we need to support this will have no user-visible consequences at all except for X, Y, and Z" type of explanation we have (apologies if I missed it). Since you?re always on the lookout for ?how can we use the Google codebase to answer questions about this?, here?s one: look for classes that implement the ?type safe enum pattern? ? ones with an inaccessible constructor, and a pile of pre-baked instances stuffed in static fields. Then ask yourself, why wasn?t this class an enum? And ask: could some of the complexity of that class go away with enhanced enums? From forax at univ-mlv.fr Wed Dec 12 18:24:59 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Wed, 12 Dec 2018 19:24:59 +0100 (CET) Subject: enhanced enums - back from the dead? In-Reply-To: <0803D1BE-38A2-4EC0-88E4-26F7B9BC2342@oracle.com> References: <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> <0803D1BE-38A2-4EC0-88E4-26F7B9BC2342@oracle.com> Message-ID: <2113875258.713481.1544639099888.JavaMail.zimbra@u-pem.fr> There is also a non empty intersection between a sum type and an enum type, obviously you can swith on both of them, but more generally an abstract enum (an enum that uses subclassing) is a specialized version of a sum type. And if we generalize sum types to allow constants, we are bridging the gap further between sum types and enums. By example, a functional list can be declared like this sum List { record Node(T value, Node next), EMPTY } ... var list = new Node<>(3, new Node<>(2, EMPTY)); R?mi > De: "Brian Goetz" > ?: "Kevin Bourrillion" > Cc: "amber-spec-experts" > Envoy?: Mercredi 12 D?cembre 2018 18:01:06 > Objet: Re: enhanced enums - back from the dead? > Yes, you could spin this feature as ?its sort of for experts?, note the > incremental complexity, and then raise the ?why bother? flag. That?s a valid > question. > But, I prefer to look at this feature in a different way. This is not, IMO, just > ?let?s make enums do more.? This is about _simplifying_ the language model by > removing gratuitous interactions between features. Enums are a constrained form > of classes, one whose instances are singletons managed by the runtime. Because > the user gives up instance control, we?re able to reward the user with things > like equals/hashCode/toString, serialization, etc. That?s a good trade. Enums > can still use most of the things that classes have ? fields, methods, > constructors, interfaces. But there?s no reason they can?t be generic, and > allowing that would reduce the inessential differences between enums and > classes. > The other asymmetry is newer; since we inferred sharp types for anonymous > classes when we did LVTI, inferring a weaker type for enum constants is now an > asymmetry (one we were aware of when we did LVTI, but the plan all along was to > align these.) > I know that I personally have run into both of these limitations at least once > in every large project I?ve ever done. You start out with an enum, for good > reasons, then you hit the limits, and then have to refactor to classes, > manually inflating the instance control boilerplate. Its frustrating, in part, > because its unnecessary. There?s nothing inconsistent about generic enums; it?s > just an accidental ?pick one? constraint that you discover when you find > yourself wanting both. > So, I prefer to look at this feature as ?regularization? or ?removing gratuitous > interactions?, rather than ?making enums more complicated.? (It?s in the same > category as some other things we?re considering, such as allowing local > methods, or refining the awful rules about static members in nested classes.) > All of these are accidental sharp edges, that create unnecessary cognitive load > for users to keep track of which features can be used in conjunction with which > others. >> On Dec 12, 2018, at 11:40 AM, Kevin Bourrillion < [ mailto:kevinb at google.com | >> kevinb at google.com ] > wrote: >> On Tue, Dec 11, 2018 at 12:25 PM Brian Goetz < [ mailto:brian.goetz at oracle.com | >> brian.goetz at oracle.com ] > wrote: >>> This uber-conservative approach seems a pretty reasonable approach to >>> me; after all, enums are a language feature, and Enum is a >>> constrained class (can't implement it directly), so it is not >>> unreasonable to define its typing wrt its supertypes specially. >>> So, let's get back to Maurizio's original question, which is: At one >>> point, we thought this feature was pretty cool, and invested some work >>> in it. Then we ran into a roadblock, and wrote it off. Now, we've got >>> a reasonable way to clear the roadblock. Which brings us back to: >>> - Do we still like the features described in JEP 301? >> What proportion of enum use cases benefit from this? Offhand, I would have to >> guess that it is less than 0.1% (and if it turned out to be far less it >> wouldn't shock me). Does anyone believe it's likely enough to be >0.1% that >> it's worth my effort to try to research that question in our codebase (which >> would take a bit of work)? >> If not, and we can stipulate that the need is rare, this means it will be a very >> special tool for very special people; a dark corner in the language feature set >> that most Java developers will never have need to know -- until they suddenly >> encounter code that uses it, upon which they need to invest an amount of effort >> understanding what is going on, even though they may have 14 years of believing >> they understood enums under their belts already. And if the need is this rare, >> this effort they put in might never be fully paid back. >> On the flip side, for a developer who does have this use case, and could benefit >> from the feature, what are the chances that developer will even know about it? >> It may be so special-purpose that we have no real reason to assume they'll know >> what to do. >> On top of this, it seems the feature apparently has a blast radius onto aspects >> of enum design that have previously been stable. >> Can a 0.1% use case ever really be worth this? >> -- >> Kevin Bourrillion | Java Librarian | Google, Inc. | [ mailto:kevinb at google.com | >> kevinb at google.com ] From maurizio.cimadamore at oracle.com Thu Dec 13 11:37:31 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Thu, 13 Dec 2018 11:37:31 +0000 Subject: enhanced enums - back from the dead? In-Reply-To: References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> Message-ID: <382b0b28-bbc2-cd1a-dfb0-8378aebaa814@oracle.com> Sorrt for the very late reply - yesterday I was writing this, but got hijacked by Xmas party :-) On 12/12/2018 16:40, Kevin Bourrillion wrote: > What proportion of enum use cases benefit from this? Offhand, I would > have to guess that it is less than 0.1% (and if it turned out to be > /far/?less it wouldn't /shock/?me). Does anyone believe it's /likely > enough/ to be >0.1% that it's worth my effort to try to research that > question in our codebase (which would take a bit of work)? Yes, this is THE question, but it is also a very tough one. I don't think this is one of those cases where one can write some kind of automated 'finder' which will tell you how often a given feature can be used in an existing code base; we were able to do that for 'var', for diamond, for lambdas. This is something very different, because looking for it essentially means looking for 'near misses' where maybe authors wished they had generics back when they wrote the enum, but they had to settle for some compromise. Example: http://hg.openjdk.java.net/jdk/jdk/file/24525070d934/src/jdk.compiler/share/classes/com/sun/source/tree/Tree.java And this falls into the 'easy' category - after all, this is still an enum, so it can be searched for. But how do we find the _classes_ that *could* be enums, but aren't simply because they are using generics (the same applies for other features not readily available in enums, e.g. local enums)? These cases are much more difficult (if not impossible) to spot! Other instance that I found in the JDK codebase is that of enums which accept some 'stringified' key - e.g. something like this: hg.openjdk.java.net/jdk/jdk/file/tip/src/java.base/share/classes/sun/security/rsa/RSAUtil.java I'm not an expert of this specific piece of code, but as in the Option enum experimement I did with javac, I believe that eager stringification is something that in principle could be cured by having more expressiveness in the enum declaration (e.g. have the key be a class, not a string!) So I think this feature is, as Brian says, about removing unnecessary discrepancies between classes and enums - discrepancies that, in the past, might have tilted code one way or another; it might be hard now to evaluate precisely how much code has been tilted - within the JDK team, it looked as though many of us had this 'moment' where we wished that something like this was indeed possible. Maurizio From dl at cs.oswego.edu Sun Dec 16 15:30:44 2018 From: dl at cs.oswego.edu (Doug Lea) Date: Sun, 16 Dec 2018 10:30:44 -0500 Subject: Flow scoping In-Reply-To: References: Message-ID: <8a4d67c2-c9cd-b5a9-05f1-1a8f4a43dde1@cs.oswego.edu> On 12/11/18 3:08 PM, Brian Goetz wrote: > > Before we get to the harder ones, can people share what made them > uncomfortable, for the ones where you marked "not comfortable with the > answer"? > As I hinted at in off-list response to Brian, my remaining discomfort is that flow-scoping could be a more general language concept, and maybe should be. I use workarounds for lack of flow-scoping all the time in concurrent code to statically ensure a single binding to locals (no field re-reads) but where some of them are conditional. The way I usually do this requires C-like declaration plus inline conditional assignment. As in this snippet from ForkJoinPool: int b, k, cap; ForkJoinTask[] a; while ((a = array) != null && (cap = a.length) > 0 && top - (b = base) > 0) { ForkJoinTask t = (ForkJoinTask) QA.getAcquire(a, k = (cap - 1) & b); ... Alternatives without inline assigns need more "{" scopes. Which is almost the same problem that flow scoping for switches addresses. If flow-scoping uniformly applied to this case (at least when using "var"), it might instead look like: while ((var a = array) != null && (var cap = a.length) > 0 && top - (var b = base) > 0) { ForkJoinTask t = (ForkJoinTask) QA.getAcquire(a, var k = (cap - 1) & b); Are there other cases in which modestly expanding support for flow scoping would help people write less-weird code? -Doug From brian.goetz at oracle.com Sun Dec 16 16:45:27 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Sun, 16 Dec 2018 11:45:27 -0500 Subject: Flow scoping In-Reply-To: <8a4d67c2-c9cd-b5a9-05f1-1a8f4a43dde1@cs.oswego.edu> References: <8a4d67c2-c9cd-b5a9-05f1-1a8f4a43dde1@cs.oswego.edu> Message-ID: > I use workarounds for lack of flow-scoping all the time in concurrent > code to statically ensure a single binding to locals (no field re-reads) > but where some of them are conditional. The way I usually do this > requires C-like declaration plus inline conditional assignment. As in > this snippet from ForkJoinPool: > > int b, k, cap; ForkJoinTask[] a; > while ((a = array) != null && > (cap = a.length) > 0 && > top - (b = base) > 0) { > ForkJoinTask t = (ForkJoinTask) > QA.getAcquire(a, k = (cap - 1) & b); > ? The two ways this code could be improved (with some help from the language) is: - All things being equal, it is nice to declare and initialize a variable in the same place; this helps readers as well as guaranteeing the data access patterns Doug is looking for. (For the audience: Doug?s motivation for coding in this ?strange? way is to ensure the code is already in SSA form, which reduces the ways in which we would accidentally fail to get the desired JIT optimizations.) - Having to pull the declarations out of for and while loops has the accidental side-effect of expanding the scope to the entire scope in which the for/while exists, even though it may not be DA in that entire region. If precise scoping is good, this is less precise scoping, which must be not as good. In other words, the scoping that Doug wants in this code is exactly the flow scoping ? let me declare a variable and initial value together in one place, be able to use it in all the places where it is DA, and be protected from accidentally using it in the places where it is not DA. The downside of this is that there are more places _in the middle of code_ where variables are introduced; it is theoretically harder to find the declarations. (But, when reading code like the above, your brain often skips over the decls anyway, because its not obvious what they are until you get into the code, so this might well be a wash.) To compensate, the more precise nature of flow scoping completely eliminates the areas of code where it might be in scope but not definitely assigned. > Alternatives without inline assigns need more "{" scopes. Which is > almost the same problem that flow scoping for switches addresses. If > flow-scoping uniformly applied to this case (at least when using "var"), > it might instead look like: > > while ((var a = array) != null && > (var cap = a.length) > 0 && > top - (var b = base) > 0) { > ForkJoinTask t = (ForkJoinTask) > QA.getAcquire(a, var k = (cap - 1) & b); Note that to support this in the flow scoping rules, we need only make some trivial modifications: - For a `var x = e` expression, x goes in both the true and false sets; - For the `if`/`while` statements, under some conditions, we can take the intersection of the true and false sets of the boolean condition in the loop header (previously provably disjoint), and put them in scope after the statement (details are a little more complicated than that, but I think only a little.) > Are there other cases in which modestly expanding support for flow > scoping would help people write less-weird code? Yes, that?s the question! The above treatment could be considered a bit of an ?abuse? of the _current_ flow scoping design, because the design to date has been so focused on conditionality. But, what you?re suggesting is that it could be ?rotated? a bit to include some useful non-conditional cases too, and I like that. We now have two examples to extrapolate from (much better than one!) ? who has more? From forax at univ-mlv.fr Sun Dec 16 17:32:36 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Sun, 16 Dec 2018 18:32:36 +0100 (CET) Subject: Flow scoping In-Reply-To: References: Message-ID: <8737197.1264911.1544981556363.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "amber-spec-experts" > Envoy?: Mardi 11 D?cembre 2018 21:08:38 > Objet: Flow scoping > (Note to observers: we did an internal survey so I could gauge the > comfort level of the EG with the flow scoping rules for pattern binding > variables.? We'll do a public survey at some point in the future.) > > Thanks for taking the time to take the quiz.? The results were pretty > good.? For the early examples, which were mostly about if-then and > short-circuiting booleans, people got the "right" answer (the answer the > current rules, at > http://cr.openjdk.java.net/~briangoetz/amber/pattern-semantics.html, > gives): > > ??? if (x instanceof String v) { > ??????? // x in scope here > ??? } > ??? else { > ??????? // x not in scope here > ??? } > > Note that we say "not in scope", not just "not DA".? There are a number > of reasons for choosing this seemingly more complicated approach. at the same time, not introducing a variable in the scope avoid tricky use cases like class A { Object a; void m(Object o) if (o instanceof A a) { System.out.println(a); // o at runtime } else { System.out.println(a); // this.a at runtime } } } so in my opinion, neither 'not being in scope' nor 'not being DA' are good strategies because as you said below we want to be able to have name reuse in switch or if ... else. I think the best is to introduce the notion of poison variable, a variable that you can reuse but that is introduced in the scope, i.e. so your have the best of the two options. > (These have been discussed here before; I'm mostly summarizing.)? One > reason is the scoping for traditional "switch" gets in the way: > > ??? switch (x) { > ??????? case String v: ...; break; > ??????? case Integer v: ...; break; > ??????? case Long v: ...; break; > ??? } > > Since the body of a switch is one big scope, when we get to the second > case, `v` would already be in scope, and have type `String`.? Similarly, > in an if-else block that represents the same computation: > > ??? if (x instanceof String v) { ... } > ??? else if (x instanceof Integer v) { ... } > ??? else if (x instanceof Long v) { ... } > > (Note too, that it is an important design constraint that the rules > allow the above two to be freely refactored into each other.) [...] R?mi From brian.goetz at oracle.com Sun Dec 16 17:49:47 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Sun, 16 Dec 2018 12:49:47 -0500 Subject: Flow scoping In-Reply-To: <8737197.1264911.1544981556363.JavaMail.zimbra@u-pem.fr> References: <8737197.1264911.1544981556363.JavaMail.zimbra@u-pem.fr> Message-ID: <9CFA9CEB-FE09-4F2F-B5DC-1B395427B037@oracle.com> > On Dec 16, 2018, at 12:32 PM, Remi Forax wrote: > > at the same time, not introducing a variable in the scope avoid tricky use cases like > > class A { > Object a; > > void m(Object o) > if (o instanceof A a) { > System.out.println(a); // o at runtime > } else { > System.out.println(a); // this.a at runtime > } > } > } > > so in my opinion, neither 'not being in scope' nor 'not being DA' are good strategies because as you said below we want to be able to have name reuse in switch or if ... else. > > I think the best is to introduce the notion of poison variable, a variable that you can reuse but that is introduced in the scope, i.e. so your have the best of the two options. Yes, we actually prototyped this, and we have some opinions about it. But, the question of shadowing (which this is really about ? what happens when a conditional variable with a non-contiguous scope shadows a field) seems secondary (if we do flow scoping, we can go either way with shadowing remediation, and if we don?t do flow scoping, it doesn?t matter.) So I?m going to suggest that come back to this issue, and focus on the core scoping questions for a bit?. From forax at univ-mlv.fr Sun Dec 16 18:00:17 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Sun, 16 Dec 2018 19:00:17 +0100 (CET) Subject: Flow scoping In-Reply-To: <9CFA9CEB-FE09-4F2F-B5DC-1B395427B037@oracle.com> References: <8737197.1264911.1544981556363.JavaMail.zimbra@u-pem.fr> <9CFA9CEB-FE09-4F2F-B5DC-1B395427B037@oracle.com> Message-ID: <1746127432.1265973.1544983217236.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "Remi Forax" > Cc: "amber-spec-experts" > Envoy?: Dimanche 16 D?cembre 2018 18:49:47 > Objet: Re: Flow scoping >> On Dec 16, 2018, at 12:32 PM, Remi Forax < [ mailto:forax at univ-mlv.fr | >> forax at univ-mlv.fr ] > wrote: >> at the same time, not introducing a variable in the scope avoid tricky use cases >> like >> class A { >> Object a; >> void m(Object o) >> if (o instanceof A a) { >> System.out.println(a); // o at runtime >> } else { >> System.out.println(a); // this.a at runtime >> } >> } >> } >> so in my opinion, neither 'not being in scope' nor 'not being DA' are good >> strategies because as you said below we want to be able to have name reuse in >> switch or if ... else. >> I think the best is to introduce the notion of poison variable, a variable that >> you can reuse but that is introduced in the scope, i.e. so your have the best >> of the two options. > Yes, we actually prototyped this, and we have some opinions about it. But, the > question of shadowing (which this is really about ? what happens when a > conditional variable with a non-contiguous scope shadows a field) seems > secondary (if we do flow scoping, we can go either way with shadowing > remediation, and if we don?t do flow scoping, it doesn?t matter.) So I?m going > to suggest that come back to this issue, and focus on the core scoping > questions for a bit?. Ok ! R?mi From brian.goetz at oracle.com Tue Dec 18 17:27:13 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 18 Dec 2018 12:27:13 -0500 Subject: enhanced enums - back from the dead? In-Reply-To: <763BEFE6-F76C-4154-939C-FF0D1E37FD40@oracle.com> References: <6487e249-3d62-b21a-fd53-92b3881edf36@oracle.com> <22f64be5-cc22-c772-de95-4a1bc75dda19@oracle.com> <343D111D-1BB7-44E6-9C2F-EC5F7DD6D79D@oracle.com> <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> <0803D1BE-38A2-4EC0-88E4-26F7B9BC2342@oracle.com> <763BEFE6-F76C-4154-939C-FF0D1E37FD40@oracle.com> Message-ID: > > In this feature, there are two changes (that we know of so far) that > might cause such observable effects. > > 1. ?Not erasing the type parameter of Enum when computing the super > type of a raw enum type. > 2. ?Changing the translation of enum constants. ?Right now, if you > have an enum constant that has a body, it gets translated as an inner > class, Foo$1. ?We would probably change that to a named nested class, > such as Foo$A. In the compiler meeting today, we realized there was another approach for (2), which was, rather than changing the declaration of the enum constant from Foo$1 to Foo$A, to instead use condy at the use site (go through something like ConstantBootstraps::enumConstant) to come up with the cast target for the cases where a client needs access to a member of an enum constant that is not inherited from a supertype (which isn't the common case.)? This would allow us to leave the translation of the declaration of enums alone, and instead perturb the use site, when the new behavior is needed.? Which removes one of our risk items. From forax at univ-mlv.fr Sun Dec 23 11:03:28 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Sun, 23 Dec 2018 12:03:28 +0100 (CET) Subject: enhanced enums - back from the dead? In-Reply-To: References: <903593e1-29ad-aa93-f82a-5436aea357a8@oracle.com> <0803D1BE-38A2-4EC0-88E4-26F7B9BC2342@oracle.com> <763BEFE6-F76C-4154-939C-FF0D1E37FD40@oracle.com> Message-ID: <491962700.1306136.1545563008076.JavaMail.zimbra@u-pem.fr> This as the drawback (real or hypothetical i don't know) to introduce an initialization dependency between the initialization of the enum (the initialization of its constants) and the initialization of the constant dynamic because at least for the first approach, you need to get the enum constant to get its class but that enum constant may not be initialized at the time the type is needed. R?mi ----- Mail original ----- > De: "Brian Goetz" > ?: "Kevin Bourrillion" > Cc: "amber-spec-experts" > Envoy?: Mardi 18 D?cembre 2018 18:27:13 > Objet: Re: enhanced enums - back from the dead? >> >> In this feature, there are two changes (that we know of so far) that >> might cause such observable effects. >> >> 1. ?Not erasing the type parameter of Enum when computing the super >> type of a raw enum type. >> 2. ?Changing the translation of enum constants. ?Right now, if you >> have an enum constant that has a body, it gets translated as an inner >> class, Foo$1. ?We would probably change that to a named nested class, >> such as Foo$A. > > In the compiler meeting today, we realized there was another approach > for (2), which was, rather than changing the declaration of the enum > constant from Foo$1 to Foo$A, to instead use condy at the use site (go > through something like ConstantBootstraps::enumConstant) to come up with > the cast target for the cases where a client needs access to a member of > an enum constant that is not inherited from a supertype (which isn't the > common case.)? This would allow us to leave the translation of the > declaration of enums alone, and instead perturb the use site, when the > new behavior is needed.? Which removes one of our risk items.