From forax at univ-mlv.fr Thu Nov 1 20:10:39 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Thu, 1 Nov 2018 21:10:39 +0100 (CET) Subject: Constant API and Q-type Message-ID: <1505326.566536.1541103039215.JavaMail.zimbra@u-pem.fr> Hi, did you have a plan for the introduction of the Q-types of valhalla in the constant API ? Introducing Q-types in the ASM API is not that simple, so i suppose you have the same kind of challenges. R?mi From brian.goetz at oracle.com Thu Nov 1 20:21:20 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 1 Nov 2018 16:21:20 -0400 Subject: Constant API and Q-type In-Reply-To: <1505326.566536.1541103039215.JavaMail.zimbra@u-pem.fr> References: <1505326.566536.1541103039215.JavaMail.zimbra@u-pem.fr> Message-ID: <554552d8-29a2-37e6-30b3-dcfb871b19c3@oracle.com> You can always use ClassDesc.ofDescriptor("QFoo;") to describe a Q-type; the only thing that has to change is the descriptor validator code. The current behavior of ClassDesc.of(String) produces an L-type; we could add ofValue(String) to choose Q-ness, or an asValue() instance method to swap L->Q, and isValue() to query L/Q. Everything else built on top (e.g., MethodType) should just work. What am I missing? On 11/1/2018 4:10 PM, Remi Forax wrote: > Hi, > did you have a plan for the introduction of the Q-types of valhalla in the constant API ? > > Introducing Q-types in the ASM API is not that simple, so i suppose you have the same kind of challenges. > > R?mi From brian.goetz at oracle.com Mon Nov 12 16:35:14 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Mon, 12 Nov 2018 17:35:14 +0100 Subject: Fwd: Lifting the restriction on the number of public classes per file References: Message-ID: This was received through amber-spec-comments. I agree with the general sentiment, especially for sealed types, where we want to define an entire sealed type hierarchy in a single compilation unit (but for whatever reason, prefer not to nest the subtypes in the super type.) There are some details to be worked out (e.g., use of the SourceFile attribute by tools). > Begin forwarded message: > > From: Francois Green > Subject: Lifting the restriction on the number of public classes per file > Date: November 11, 2018 at 10:09:06 PM GMT+1 > To: amber-spec-comments at openjdk.java.net > > In the face of the changes in code style that records will bring about, has > there been renewed discussion about lifting the restriction? > > public interface Blue; > public record IKB() implements Blue; > public record Azure() implements Blue; > public record Royal() implements Blue; > > Having to place each line in the code above in its own file seems harsh. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Wed Nov 14 11:51:26 2018 From: forax at univ-mlv.fr (forax at univ-mlv.fr) Date: Wed, 14 Nov 2018 12:51:26 +0100 (CET) Subject: Constant API and Q-type In-Reply-To: <554552d8-29a2-37e6-30b3-dcfb871b19c3@oracle.com> References: <1505326.566536.1541103039215.JavaMail.zimbra@u-pem.fr> <554552d8-29a2-37e6-30b3-dcfb871b19c3@oracle.com> Message-ID: <989893400.677078.1542196286749.JavaMail.zimbra@u-pem.fr> ----- Mail original ----- > De: "Brian Goetz" > ?: "Remi Forax" , "Vicente Romero" > Cc: "amber-spec-experts" > Envoy?: Jeudi 1 Novembre 2018 21:21:20 > Objet: Re: Constant API and Q-type > You can always use ClassDesc.ofDescriptor("QFoo;") to describe a Q-type; > the only thing that has to change is the descriptor validator code. > > The current behavior of ClassDesc.of(String) produces an L-type; we > could add ofValue(String) to choose Q-ness, or an asValue() instance > method to swap L->Q, and isValue() to query L/Q. Everything else built > on top (e.g., MethodType) should just work. > > What am I missing? sorry for the delay ... The same issue as with ASM, those methods are an afterthought that shows a bias to the current view of the world, making the API a little 'of' for the world where reference type and value type both co-exist. ClassDesc.of() should be renamed to ofJavaName() and takes a second parameter indicating if it's a reference or a value, ofDescriptor is ok and we should have two other methods ofReferenceInternalName() and ofValueInternalName() that takes an internal names. I maybe the only one to have this issue but while testing this API, knowing which methods use a Java name or an internal name was not obvious for me. Do we really need to have methods that takes a Java name (the dotted variant) ? R?mi > > > > On 11/1/2018 4:10 PM, Remi Forax wrote: >> Hi, >> did you have a plan for the introduction of the Q-types of valhalla in the >> constant API ? >> >> Introducing Q-types in the ASM API is not that simple, so i suppose you have the >> same kind of challenges. >> > > R?mi From amaembo at gmail.com Thu Nov 22 10:18:19 2018 From: amaembo at gmail.com (Tagir Valeev) Date: Thu, 22 Nov 2018 17:18:19 +0700 Subject: Can switch expression be compile-time constant? Message-ID: Hello! It seems that switch expressions are not compiled as compile-time constant (using Java12 ea builds). E.g.: public class Constant { public static void main(String[] args) { int x = switch(0) {case 1 -> 2; case 2 -> 3; default -> 1;}; int y = 0 == 1 ? 2 : 0 == 2 ? 3 : 1; System.out.println(x); } } The equivalent conditional expression is folded into constant 1, but switch is present in bytecode as is. Is it deliberate decision to exclude switches from compile-time constant? With best regards, Tagir Valeev From brian.goetz at oracle.com Thu Nov 22 13:03:27 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 22 Nov 2018 08:03:27 -0500 Subject: Can switch expression be compile-time constant? In-Reply-To: References: Message-ID: <0C5445AA-7707-4846-9C6C-4F18D5C6B50D@oracle.com> Yes, this was a deliberate choice. Sent from my iPad > On Nov 22, 2018, at 5:18 AM, Tagir Valeev wrote: > > Hello! > > It seems that switch expressions are not compiled as compile-time > constant (using Java12 ea builds). E.g.: > > public class Constant { > public static void main(String[] args) { > int x = switch(0) {case 1 -> 2; case 2 -> 3; default -> 1;}; > int y = 0 == 1 ? 2 : 0 == 2 ? 3 : 1; > System.out.println(x); > } > } > > The equivalent conditional expression is folded into constant 1, but > switch is present in bytecode as is. Is it deliberate decision to > exclude switches from compile-time constant? > > With best regards, > Tagir Valeev From maurizio.cimadamore at oracle.com Thu Nov 22 13:50:38 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Thu, 22 Nov 2018 13:50:38 +0000 Subject: Can switch expression be compile-time constant? In-Reply-To: <0C5445AA-7707-4846-9C6C-4F18D5C6B50D@oracle.com> References: <0C5445AA-7707-4846-9C6C-4F18D5C6B50D@oracle.com> Message-ID: <52000c45-2c2d-afe3-2a29-5f2a92b58e47@oracle.com> One small point, as I've recently discussed this with Jan. Tagir, you are asking two questions :-) 1) Should a switch expression (of the right kind) be treated as a 'constant expression' (as per 15.28) ? 2) Should javac opportunistically fold away dead code when it can detect so? The answer to (1), as Brian says, is a deliberate 'no'; otherwise, you could have switch expression appearing after a 'case' too... The answer to (2) is, well, javac *could*. But it's completely implementation-dependent as some optimization in the generated bytecode will take place or not. That said, (2) is partially related to our efforts in JEP 303 - maybe there are cases in which it could make sense to treat certain switch expressions as 'trackable' by javac (which doesn't mean constant as per 15.28!!), so that intrinsification can happen. Maurizio On 22/11/2018 13:03, Brian Goetz wrote: > Yes, this was a deliberate choice. > > Sent from my iPad > >> On Nov 22, 2018, at 5:18 AM, Tagir Valeev wrote: >> >> Hello! >> >> It seems that switch expressions are not compiled as compile-time >> constant (using Java12 ea builds). E.g.: >> >> public class Constant { >> public static void main(String[] args) { >> int x = switch(0) {case 1 -> 2; case 2 -> 3; default -> 1;}; >> int y = 0 == 1 ? 2 : 0 == 2 ? 3 : 1; >> System.out.println(x); >> } >> } >> >> The equivalent conditional expression is folded into constant 1, but >> switch is present in bytecode as is. Is it deliberate decision to >> exclude switches from compile-time constant? >> >> With best regards, >> Tagir Valeev From kevinb at google.com Mon Nov 26 19:43:37 2018 From: kevinb at google.com (Kevin Bourrillion) Date: Mon, 26 Nov 2018 11:43:37 -0800 Subject: Lifting the restriction on the number of public classes per file In-Reply-To: References: Message-ID: Sorry for delay. Can we unpack the "for whatever reason" part? For what reason? Unsurprisingly to anyone, we actually hard-ban this practice here. Multiple top-level classes per file just make code harder to find; what are the advantages? On Mon, Nov 12, 2018 at 8:35 AM Brian Goetz wrote: > This was received through amber-spec-comments. > > I agree with the general sentiment, especially for sealed types, where we > want to define an entire sealed type hierarchy in a single compilation unit > (but for whatever reason, prefer not to nest the subtypes in the super > type.) There are some details to be worked out (e.g., use of the > SourceFile attribute by tools). > > Begin forwarded message: > > *From: *Francois Green > *Subject: **Lifting the restriction on the number of public classes per > file* > *Date: *November 11, 2018 at 10:09:06 PM GMT+1 > *To: *amber-spec-comments at openjdk.java.net > > In the face of the changes in code style that records will bring about, has > there been renewed discussion about lifting the restriction? > > public interface Blue; > public record IKB() implements Blue; > public record Azure() implements Blue; > public record Royal() implements Blue; > > Having to place each line in the code above in its own file seems harsh. > > > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Mon Nov 26 22:03:43 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Mon, 26 Nov 2018 23:03:43 +0100 (CET) Subject: Lifting the restriction on the number of public classes per file In-Reply-To: References: Message-ID: <148949071.278732.1543269823569.JavaMail.zimbra@u-pem.fr> > De: "Kevin Bourrillion" > ?: "Brian Goetz" > Cc: "amber-spec-experts" > Envoy?: Lundi 26 Novembre 2018 20:43:37 > Objet: Re: Lifting the restriction on the number of public classes per file > Sorry for delay. Can we unpack the "for whatever reason" part? For what reason? > Unsurprisingly to anyone, we actually hard-ban this practice here. Multiple > top-level classes per file just make code harder to find; what are the > advantages? There are two kind of nesting, - nesting for scoping, like with inner classes - nesting for sealing, like we are part of the same hierarchy. We don't want sealing to imply scoping. One solution is to allow multiple public top level classes as Brian propose, the other solution is to allow sealing without scoping with by example a keyword. We can introduce a new keyword (let's call it 'whatever') which means this is a subtype and a nestmate but not an inner class, sealed interface Expr { whatever record Add(Expr left, Expr right); whatever record Value(int value); } >From outside Expr.java, Add and Value are toplevel classes that can be used like this: Expr expr = new Add(new Value(3), new Value(39)); should work. R?mi > On Mon, Nov 12, 2018 at 8:35 AM Brian Goetz < [ mailto:brian.goetz at oracle.com | > brian.goetz at oracle.com ] > wrote: >> This was received through amber-spec-comments. >> I agree with the general sentiment, especially for sealed types, where we want >> to define an entire sealed type hierarchy in a single compilation unit (but for >> whatever reason, prefer not to nest the subtypes in the super type.) There are >> some details to be worked out (e.g., use of the SourceFile attribute by tools). >>> Begin forwarded message: >>> From: Francois Green < [ mailto:francois.green at gmail.com | >>> francois.green at gmail.com ] > >>> Subject: Lifting the restriction on the number of public classes per file >>> Date: November 11, 2018 at 10:09:06 PM GMT+1 >>> To: [ mailto:amber-spec-comments at openjdk.java.net | >>> amber-spec-comments at openjdk.java.net ] >>> In the face of the changes in code style that records will bring about, has >>> there been renewed discussion about lifting the restriction? >>> public interface Blue; >>> public record IKB() implements Blue; >>> public record Azure() implements Blue; >>> public record Royal() implements Blue; >>> Having to place each line in the code above in its own file seems harsh. > -- > Kevin Bourrillion | Java Librarian | Google, Inc. | [ mailto:kevinb at google.com | > kevinb at google.com ] -------------- next part -------------- An HTML attachment was scrubbed... URL: From john.r.rose at oracle.com Tue Nov 27 02:04:01 2018 From: john.r.rose at oracle.com (John Rose) Date: Mon, 26 Nov 2018 18:04:01 -0800 Subject: Lifting the restriction on the number of public classes per file In-Reply-To: References: Message-ID: <1E792AF9-9C38-4279-A4F5-9C99F3F11122@oracle.com> On Nov 26, 2018, at 11:43 AM, Kevin Bourrillion wrote: > > Multiple top-level classes per file just make code harder to find; what are the advantages? I suppose the main advantage (as suggested in the examples) would be flexibility of naming. With inner classes your names are pkg.NH.{A,B,C?}, where the outer class NH serves as nest host and also scope. With the proposal the names could also be pkg.{A,B,C?}, with a common nesthost (A or NH). In source code the simple names can be obtained by using an import which mentions NH: `` import pkg.NH.* ``. So there's no great source-level advantage to having the top-level names (package members) instead of the nested names (class members). Maybe there's some advantage in having reflection avoid mention the "NH$"? The hardest downside to the proposal is that IDEs and some javac compilation modes (source-paths) don't know where to look for a file containing pkg.A, if the classfile pkg/A.class has not yet been generated. I suppose one answer to that is, "the source path mode doesn't work on such top-level auxiliary nest membrers". And IDEs have to sniff at NH.java a little more carefully to determine if NH.java contains extra definitions. But these issues already exist and have a user model, right? On the whole, I don't see a big downside, but neither do I see a killer use case for pkg.A that isn't covered by pkg.NH.A. ? John -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Tue Nov 27 07:40:22 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Tue, 27 Nov 2018 08:40:22 +0100 (CET) Subject: Lifting the restriction on the number of public classes per file In-Reply-To: <1E792AF9-9C38-4279-A4F5-9C99F3F11122@oracle.com> References: <1E792AF9-9C38-4279-A4F5-9C99F3F11122@oracle.com> Message-ID: <792519235.319884.1543304422573.JavaMail.zimbra@u-pem.fr> > De: "John Rose" > ?: "Kevin Bourrillion" > Cc: "amber-spec-experts" > Envoy?: Mardi 27 Novembre 2018 03:04:01 > Objet: Re: Lifting the restriction on the number of public classes per file > On Nov 26, 2018, at 11:43 AM, Kevin Bourrillion < [ mailto:kevinb at google.com | > kevinb at google.com ] > wrote: >> Multiple top-level classes per file just make code harder to find; what are the >> advantages? > I suppose the main advantage (as suggested in the examples) would be > flexibility of naming. With inner classes your names are pkg.NH.{A,B,C?}, > where the outer class NH serves as nest host and also scope. With the > proposal the names could also be pkg.{A,B,C?}, with a common nesthost > (A or NH). > In source code the simple names can be obtained by using an import > which mentions NH: `` import pkg.NH.* ``. So there's no great source-level > advantage to having the top-level names (package members) instead > of the nested names (class members). Maybe there's some advantage > in having reflection avoid mention the "NH$"? > The hardest downside to the proposal is that IDEs and some javac > compilation modes (source-paths) don't know where to look for a file > containing pkg.A, if the classfile pkg/A.class has not yet been generated. > I suppose one answer to that is, "the source path mode doesn't work on > such top-level auxiliary nest membrers". And IDEs have to sniff at NH.java > a little more carefully to determine if NH.java contains extra definitions. > But these issues already exist and have a user model, right? given that you can already have several compilation unit in one file (only marked public), i believe the answer is yes. > On the whole, I don't see a big downside, but neither do I see a killer > use case for pkg.A that isn't covered by pkg.NH.A. The use case for pkg.A instead of pkg.MH.A is refactoring, i.e. refactor an existing hierarchy of top level types to use a sealed interface. > ? John R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From maurizio.cimadamore at oracle.com Tue Nov 27 10:29:08 2018 From: maurizio.cimadamore at oracle.com (Maurizio Cimadamore) Date: Tue, 27 Nov 2018 10:29:08 +0000 Subject: Lifting the restriction on the number of public classes per file In-Reply-To: <1E792AF9-9C38-4279-A4F5-9C99F3F11122@oracle.com> References: <1E792AF9-9C38-4279-A4F5-9C99F3F11122@oracle.com> Message-ID: <97a0347d-7bcb-cf4d-8c77-a652705231dc@oracle.com> On 27/11/2018 02:04, John Rose wrote: > In source code the simple names can be obtained by using an import > which mentions NH: ?`` import pkg.NH.* ``. ?So there's no great > source-level > advantage to having the top-level names (package members) instead > of the nested names (class members). ?Maybe there's some advantage > in having reflection avoid mention the "NH$"? > I would add javadoc to the list of things that end up being suboptimal when using a nested hierarchy. Again, not a game changer, but mentioning here for completeness. Maurizio From brian.goetz at oracle.com Tue Nov 27 17:47:25 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 27 Nov 2018 12:47:25 -0500 Subject: Lifting the restriction on the number of public classes per file In-Reply-To: References: Message-ID: When we have sealed classes and records, it will be practical and sensible to declare entire related hierarchies together: -- Shape.java ??? sealed interface Shape { } ??? record Circle(Point center, int radius) extends Shape; ??? record Square(Point corner, int length) extends Shape; -- Not only is it inconvenient for the writer to require each get their own source file, but more importantly, it is bad for the reader -- separating these declarations makes it hard to see what is a group of related entities.? (Imagine if we required a separate source file for each enum constant; it would be much harder to see the structure of an enum.)? So we want to encourage this sort of co-declaration. It is an easy rejoinder to say "Then just declare them as nested in the Shape, and use static imports to clean up the use-site damage from that."? But this is a bad answer, because, as Remi and Maurizio point out, this affects _the structure of your public API_.? But that means we're putting users in a place where they get to pick two of the following three features: ?- Use sealing ?- Declare a sealed hierarchy with its source together ?- Declare a flat API I've seen API style guides that forbid the use of public nested classes.? And while I don't personally program this way, I think its a not-ridiculous house style.? But then people in this situation have to write in a less readable, less maintainable way. So, my "for whatever reason" means: People should get to choose the shape of their exported APIs (and flat is a valid shape preference), and not be forced into a particular shape because they want to use sealing.? The API shape above -- a sum of records -- is one we should encourage, not discourage. On 11/26/2018 2:43 PM, Kevin Bourrillion wrote: > Sorry for delay. Can we unpack the "for whatever reason" part?? For > what reason? > > Unsurprisingly to anyone, we actually hard-ban this practice here. > Multiple top-level classes per file just make code harder to find; > what are the advantages? > > On Mon, Nov 12, 2018 at 8:35 AM Brian Goetz > wrote: > > This was received through amber-spec-comments. > > I agree with the general sentiment, especially for sealed types, > where we want to define an entire sealed type hierarchy in a > single compilation unit (but for whatever reason, prefer not to > nest the subtypes in the super type.) ?There are some details to > be worked out (e.g., use of the SourceFile attribute by tools). > >> Begin forwarded message: >> >> *From: *Francois Green > > >> *Subject: **Lifting the restriction on the number of public >> classes per file* >> *Date: *November 11, 2018 at 10:09:06 PM GMT+1 >> *To: *amber-spec-comments at openjdk.java.net >> >> >> In the face of the changes in code style that records will bring >> about, has >> there been renewed discussion about lifting the restriction? >> >> ???public interface Blue; >> ???public record IKB() implements Blue; >> ???public record Azure() implements Blue; >> ???public record Royal() implements Blue; >> >> Having to place each line in the code above in its own file seems >> harsh. > > > > -- > Kevin Bourrillion?|?Java Librarian |?Google, Inc.?|kevinb at google.com > -------------- next part -------------- An HTML attachment was scrubbed... URL: From cushon at google.com Tue Nov 27 19:13:22 2018 From: cushon at google.com (Liam Miller-Cushon) Date: Tue, 27 Nov 2018 14:13:22 -0500 Subject: Lifting the restriction on the number of public classes per file In-Reply-To: References: Message-ID: On Mon, Nov 26, 2018 at 9:05 PM John Rose wrote: > The hardest downside to the proposal is that IDEs and some javac > compilation modes (source-paths) don't know where to look for a file > containing pkg.A, if the classfile pkg/A.class has not yet been generated. > I agree, and would add that making it harder to locate declarations also affects humans reading the code, and other tooling besides the compiler and IDEs (e.g. import and dependency management tools). > I suppose one answer to that is, "the source path mode doesn't work on > such top-level auxiliary nest membrers". And IDEs have to sniff at NH.java > a little more carefully to determine if NH.java contains extra definitions. > But these issues already exist and have a user model, right? > The issue today only exists within package boundaries, and isn't always well handled (by tooling, or by users' mental models). Making it more common isn't certain to result in it being better supported and understood. On Tue, Nov 27, 2018 at 12:47 PM Brian Goetz wrote: > It is an easy rejoinder to say "Then just declare them as nested in the > Shape, and use static imports to clean up the use-site damage from that." > But this is a bad answer, because, as Remi and Maurizio point out, this > affects _the structure of your public API_. But that means we're putting > users in a place where they get to pick two of the following three features: > Will imports always be necessary when pattern matching on sealed types, or could there be something like the provision for enum switches? -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Tue Nov 27 19:27:30 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 27 Nov 2018 14:27:30 -0500 Subject: Lifting the restriction on the number of public classes per file In-Reply-To: References: Message-ID: > Will imports always be necessary when pattern matching on sealed types, > or could there be something like the provision for enum switches? We could expose a model where a sealed nest is more like an enum, and the nest members are more like enum constants, in which case using the same rules as for enums would make sense.? If we thought that was the right model for sealing.? If we end up not relaxing the rules regarding aux classes, we should consider this for switches (but it doesn't really address the more general API design question.) It is true that in many other languages, sealed types are mostly intended for "sums of products", which is a reasonable 80/20 target.? Though I'd rather see a deeper / more orthogonal treatment (though that does force us to work through more details.) > The hardest downside to the proposal There's not a proposal, yet...? There's just a discussion, proximately motivated by an external comment, but one that has been on the drawing board for a while, since we started to look at more concise ways to express sum and product types. From brian.goetz at oracle.com Tue Nov 27 22:20:54 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Tue, 27 Nov 2018 17:20:54 -0500 Subject: Sealed types Message-ID: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> Since we?re already discussing one of the consequences of sealed types, let?s put the whole story on the table. These are my current thoughts, but there?s room in the design space to wander a bit. *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. /Note:/ It is probably desirable to ship this with records, though it could be shipped before or after. *Declaration.* We specify that a class is sealed by applying the |sealed| modifier to a class, abstract class, interface, or record: |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. *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.) Permitted subtypes must belong to the same module (or, if not in a module, the same package and protection domain.) /Note:/ It is superficially tempting to have a relaxed but less explicit form, say which allows 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. 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_SEALED| modifier (we could choose to overload |ACC_FINAL| for bit preservation), and a |Sealed| attribute which contains a list of permitted subtypes (similar in structure to the nestmate attributes.) *Transitivity.* Sealing is transitive; unless otherwise specified, an abstract subtype of a sealed type is implicitly sealed, and a concrete subtype of a sealed type is implicitly final. This can be reversed by explicitly modifying the subtype with the |non-sealed| or |non-final| modifiers. Unsealing a subtype in a hierarchy doesn?t undermine the 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 transitivity, requiring sealing to be opted into at all levels. This is widely believed to be a source of bugs; it is rare that one actually wants a subtype of a sealed type to not be sealed. I suspect the reasoning in Scala was, at least partially, the desire to not make up a new keyword for ?not sealed?. This is understandable, 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: |sealed interface ConstantDesc permits String, Integer, Float, Long, Double, ClassDesc, MethodTypeDesc, MethodHandleDesc, DynamicConstantDesc { } sealed interface ClassDesc extends ConstantDesc permits PrimitiveClassDescImpl, ReferenceClassDescImpl { } private class PrimitiveClassDescImpl implements ClassDesc { } private class ReferenceClassDescImpl implements ClassDesc { } sealed interface MethodTypeDesc extends ConstantDesc permits MethodTypeDescImpl { } sealed interface MethodHandleDesc extends ConstantDesc permits DirectMethodHandleDesc, MethodHandleDescImpl { } sealed interface DirectMethodHandleDesc extends MethodHandleDesc permits DirectMethodHandleDescImpl // designed for subclassing non-sealed class DynamicConstantDesc extends ConstantDesc { ... } | *Enforcement.* Both the compiler and JVM should enforce sealing. /Note:/ 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, 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 probably be considered part of the spec, and 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.) /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. ? /Syntactic alternative:/ Rather than inventing a new modifier (which then needs a negation modifier), we could generalize the meaning of |final|, such as: |final class C { } // truly final final interface Node // explicit version permits ANode, BNode { } non-final class ANode implements Node { } // opt-out of transitivity final interface Node // inferred version permits { } | This eliminates both |sealed| and |non-sealed| as modifiers. The downside is, of course, that it will engender some ?who moved my cheese? reactions. (We currently have separate spellings for |extends| and |implements|; some think this was a mistake, and it surely complicated things when we got to generics, as we created an asymmetry with ||. The move of retconning |final| avoids replicating this mistake.) This is in line with the suggested retcon at the classfile level as well. ? -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Fri Nov 30 01:12:11 2018 From: kevinb at google.com (Kevin Bourrillion) Date: Thu, 29 Nov 2018 17:12:11 -0800 Subject: Sealed types In-Reply-To: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> Message-ID: On Tue, Nov 27, 2018 at 2:21 PM Brian Goetz wrote: *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. > Clearly enums would be implicitly sealed (a redundant `sealed` keyword presumably being ignored), and I assume we'll make sure they appear/behave exactly the same as other sealed classes. 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. > Today, only non-interface classes can get something very close to that protection, and I think that is the only reason Guava's `ImmutableList` is a class instead of an interface. Which has been a bummer, since that `class` keyword has given many users the wrong impression of how it's meant to be used. 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. > Another example application: compiler (or static analysis tool) can recognize bad casting conversions from/to the sealed type - maybe even *all* bad casting conversions when no subtype "unseals"? This is minor, but of course, anything that makes more code recognizable as erroneous is also going to help auto-complete work better and so on. *Note:* It is probably desirable to ship this with records, though it could > be shipped before or after. > Interesting: I would have expected you to say exactly that but for pattern-matching instead of records. > *Declaration.* We specify that a class is sealed by applying the sealed > modifier to a class, abstract class, interface, or record: > > 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. > I suspect we ought to *recommend* use of `permits` as a kindness to users who aren't always looking at javadoc, so they can actually see what to switch over. Maybe requiring `permits` always is too much though (it also precludes anonymous subtypes, but then again those are of limited value anyway, aren't they?). So back to the current state. I'm not 100% following why `permits` shouldn't just be additive when present. That avoids the "cliff" and we do generally trust source files to not misuse *themselves*. Or, if not additive, but we end up reusing the `final` keyword in the way shown at the bottom of this email, then we could at least allow `permits **, TypeA, TypeB` which is maybe nearly as good. *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.) Permitted subtypes must > belong to the same module (or, if not in a module, the same package and > protection domain.) > > *Note:* It is superficially tempting to have a relaxed but less explicit > form, say which allows 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. 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_SEALED modifier (we could choose to overload ACC_FINAL for bit > preservation), and a Sealed attribute which contains a list of permitted > subtypes (similar in structure to the nestmate attributes.) > As before: I would expect any final class or enum to have ACC_SEALED set, correct? *Transitivity.* Sealing is transitive; unless otherwise specified, an > abstract subtype of a sealed type is implicitly sealed, and a concrete > subtype of a sealed type is implicitly final. > This can be reversed by explicitly modifying the subtype with the non-sealed > or non-final modifiers. > (FWIW, I found the idea of an unsealed subtype of a sealed supertype massively confusing for a good while until I finally figured out why there's nothing wrong with it.) Do you mean the last statement above as "respectively" (non-sealed can counteract implicit sealed of abstract; non-final can counteract implicit final of concrete), or does `non-sealed` also work to counteract the implicit final of a concrete class? For that matter shouldn't `sealed` implicit undo the implicit `final` of a concrete class? I admit to still being fairly confused right now. (Syntax: I assume that the syntax is still malleable and not what needs to be debated here and now. Nevertheless, I don't want to miss my chance to object to the hyphenation for the record. These won't be seen as two new keywords, but as a modifier-modifier, and users will not understand when `non-` works and when it doesn't. I think `unsealed` and `nonfinal` keywords would be better. `nonfinal` still has its own problems of seeming more widely applicable than it is...) Unsealing a subtype in a hierarchy doesn?t undermine the 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.) > Having a hard time understanding this part. Trying to map your FNFE analogy over here, I get something like "You can pattern-match on the Sub type in a separate case from Super if you want, but don't have to." But I don't see how it's "knowing about unsealedness" that gives you that; isn't it just "knowing what some of Super's subtypes are", whether Super is sealed or not? > *Javadoc.* The list of permitted subtypes should probably be considered > part of the spec, and 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.) > If any subtype is less accessible than whatever access level Javadoc is building for, it's name really should not be shown, and if it's anonymous (if that's even allowed), then it's name *can't* even be shown. I think that all the reader of the doc needs to know is "if matching all the listed subtypes, do I or don't I also need a case for this type itself?" and that could happen *either* for the preceding reason or because the type itself is concrete. > *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. > Whatever you decide here, at least don't think of the current rule as "silly". It's not silly! It makes code findable. We all do things like click through github to find source for a class, etc. We should think very very hard before violating that. Users have two choices already; the fact that sometimes neither of the two existing options is perfect doesn't necessarily mean we need a third option. It might just mean that sometimes you just grumble and accept the lesser of the two evils. *Syntactic alternative:* Rather than inventing a new modifier (which then > needs a negation modifier), we could generalize the meaning of final, > such as: > > final class C { } // truly final > > final interface Node // explicit version > permits ANode, BNode { } > > non-final class ANode implements Node { } // opt-out of transitivity > > final interface Node // inferred version > permits { } > > This eliminates both sealed and non-sealed as modifiers. The downside is, > of course, that it will engender some ?who moved my cheese? reactions. (We > currently have separate spellings for extends and implements; some think > this was a mistake, and it surely complicated things when we got to > generics, as we created an asymmetry with . The move of > retconning final avoids replicating this mistake.) This is in line with > the suggested retcon at the classfile level as well. > There might be some problem with this I guess, but right now it appeals to me very much. -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Nov 30 01:42:58 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Thu, 29 Nov 2018 20:42:58 -0500 Subject: Sealed types In-Reply-To: References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> Message-ID: <959AF02E-9041-4085-8450-D307EAD4892C@oracle.com> > > Note: It is probably desirable to ship this with records, though it could be shipped before or after. > Interesting: I would have expected you to say exactly that but for pattern-matching instead of records. All these features work nicely together, but the kinship I was thinking of was that sealed types are sum types, and records are product types, and sums of products are a particularly useful pattern. But, you are right that to really get the benefit, you also want pattern matching over the sum. In reality, all of these features are good on their own and better with each other, so this is a minor point. > > I suspect we ought to recommend use of `permits` as a kindness to users who aren't always looking at javadoc, so they can actually see what to switch over. Maybe requiring `permits` always is too much though (it also precludes anonymous subtypes, but then again those are of limited value anyway, aren't they?). I worry that requiring this will feel heavyweight, especially when the list is long AND the classes being declared are compact (like records). Take the following example, and scale it to 26 types; I can easily imagine being irritated that I have to list out A .. Z twice. sealed interface X permits A, B, C { } record A() implements X {} record B(int b) implements X { } record C(int c, int d) implements X { } But, as a style suggestion, it becomes more reasonable; I could easily imagine the JDK doing this just for clarity, as we tend to be less concerned with concision than the average developer. > > So back to the current state. I'm not 100% following why `permits` shouldn't just be additive when present. That avoids the "cliff" and we do generally trust source files to not misuse themselves. In nearly all the use cases I have in mind, either the subtypes are declared all together, or they are strewn about the file system. I have a harder time imagining the case where you have 20 that are co-declared, and one that is elsewhere; that?s the case in which the additive interpretation would pay for itself. Can you think of situations in which this would arise regularly? My concern is not about the source misusing itself, as much as the reader being able to reason about it more easily. I like the idea that if there is a permits clause, it is exhaustive; if you leave it out, the compiler infers the obvious thing. Much less to reason about when reading code. If the source file is large, readers shouldn?t have to trawl through the whole file just to find out ?permits A, B? isn?t an exhaustive list. > > > Classfile. In the classfile, a sealed type is identified with an ACC_SEALED modifier (we could choose to overload ACC_FINAL for bit preservation), and a Sealed attribute which contains a list of permitted subtypes (similar in structure to the nestmate attributes.) > > As before: I would expect any final class or enum to have ACC_SEALED set, correct? If we plunk for the bit (these bits are expensive), then there will surely be legacy classfiles that say FINAL but not SEALED, so we have to be prepared to see that. If we merge the bits, the risk is that classfile interpreters may (reasonably) interpret final as ?no subtypes?, even thought the classfile is strongly versioned. I think I err on the side of bit preservation. > > > Transitivity. Sealing is transitive; unless otherwise specified, an abstract subtype of a sealed type is implicitly sealed, and a concrete subtype of a sealed type is implicitly final. > > This can be reversed by explicitly modifying the subtype with the non-sealed or non-final modifiers. > > (FWIW, I found the idea of an unsealed subtype of a sealed supertype massively confusing for a good while until I finally figured out why there's nothing wrong with it.) An unsealed subCLASS of a sealed super type can be a very useful move (see DynamicConstnatDesc in JEP-334), but an unsealed subINTERFACE is harder to grok, because interfaces (absent sealing) lack the ability to constrain their subclasses very much (no protected members, no final methods, no nonpublic constructors.) > > Do you mean the last statement above as "respectively" (non-sealed can counteract implicit sealed of abstract; non-final can counteract implicit final of concrete), or does `non-sealed` also work to counteract the implicit final of a concrete class? For that matter shouldn't `sealed` implicit undo the implicit `final` of a concrete class? I admit to still being fairly confused right now. Your confusion is a good argument to consider the syntactic choice of retconning final, rather than adding sealing :) The idea is that without utterance to the contrary, a subINTERFACE of a sealed type is implicitly sealed, and a subCLASS of a sealed type is implicitly final. This could be reversed by uttering the non-{sealed,final}, incantation. (You could also explicitly re-seal a subtype, which would be useful to provide a permits list.) > > (Syntax: I assume that the syntax is still malleable and not what needs to be debated here and now. Nevertheless, I don't want to miss my chance to object to the hyphenation for the record. These won't be seen as two new keywords, but as a modifier-modifier, and users will not understand when `non-` works and when it doesn't. I think `unsealed` and `nonfinal` keywords would be better. `nonfinal` still has its own problems of seeming more widely applicable than it is?) The rationale here is that it is reasonable to assume that we might at some point want the opposites of modifiers like abstract, final, static, etc. We could of course make up ad-hoc names, but not only is that more names and more tokens the parser has to conditionally treat specially, but even for the opposite of ?final? we would probably need two, since final means both ?immutable? and ?non-subclassable/overridable?, and its pretty hard to think of a word that covers both (mutable is good for the first; open is good for the second.) Having a mechanical rule also reduces the degree of bike shed debate. Hence, my preference for this approach, but I understand it might not be universally admired. (But, Kevin is right ? not the place for this discussion. Everyone, please don?t weigh in on this further now.) > > Unsealing a subtype in a hierarchy doesn?t undermine the 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.) > > Having a hard time understanding this part. Trying to map your FNFE analogy over here, I get something like "You can pattern-match on the Sub type in a separate case from Super if you want, but don't have to." But I don't see how it's "knowing about unsealedness" that gives you that; isn't it just "knowing what some of Super's subtypes are", whether Super is sealed or not? Right, just saying that having a unsealed subtype doesn?t burden clients with having to know the details if they don?t want. They can pretend everything is sealed, and still exhaustively switch over it. > > > Javadoc. The list of permitted subtypes should probably be considered part of the spec, and 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.) > > If any subtype is less accessible than whatever access level Javadoc is building for, it's name really should not be shown, and if it's anonymous (if that's even allowed), then it's name can't even be shown. I think that all the reader of the doc needs to know is "if matching all the listed subtypes, do I or don't I also need a case for this type itself?" and that could happen either for the preceding reason or because the type itself is concrete. Reasonable. The key is that the doc should say ?there may be others?, so that users don?t get surprised when their seemingly-exhaustive switch fails. > > Syntactic alternative: Rather than inventing a new modifier (which then needs a negation modifier), we could generalize the meaning of final, such as: > > final class C { } // truly final > > final interface Node // explicit version > permits ANode, BNode { } > > non-final class ANode implements Node { } // opt-out of transitivity > > final interface Node // inferred version > permits { } > This eliminates both sealed and non-sealed as modifiers. The downside is, of course, that it will engender some ?who moved my cheese? reactions. (We currently have separate spellings for extends and implements; some think this was a mistake, and it surely complicated things when we got to generics, as we created an asymmetry with . The move of retconning finalavoids replicating this mistake.) This is in line with the suggested retcon at the classfile level as well. > > There might be some problem with this I guess, but right now it appeals to me very much. > Yeah, me too. -------------- next part -------------- An HTML attachment was scrubbed... URL: From forax at univ-mlv.fr Fri Nov 30 07:58:59 2018 From: forax at univ-mlv.fr (Remi Forax) Date: Fri, 30 Nov 2018 08:58:59 +0100 (CET) Subject: Sealed types In-Reply-To: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> Message-ID: <1747463018.1229943.1543564739664.JavaMail.zimbra@u-pem.fr> > De: "Brian Goetz" > ?: "amber-spec-experts" > Envoy?: Mardi 27 Novembre 2018 23:20:54 > Objet: Sealed types > Since we?re already discussing one of the consequences of sealed types, let?s > put the whole story on the table. These are my current thoughts, but there?s > room in the design space to wander a bit. > 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. > Note: It is probably desirable to ship this with records, though it could be > shipped before or after. > Declaration. We specify that a class is sealed by applying the sealed modifier > to a class, abstract class, interface, or record: > 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 works for anonymous class declared in the same compilation unit ? sealed interface Foo { static Foo create() { return new Foo() { ... }; // ok } } the compiler add Foo$1 in the permit list. - it works for functional interface for lambda in the same compilation unit ? sealed interface Fun { int apply(int value); static Fun identity() { return x -> x; // ok } } what exactly the permit list contains ?? (dynamic nestmate/sealed class to the rescue ?) And in another compilation unit Fun fun = x -> 2 * x; // rejected because Fun is sealed ? > 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.) Permitted subtypes must belong to the same module (or, if > not in a module, the same package and protection domain.) > Note: It is superficially tempting to have a relaxed but less explicit form, say > which allows 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. 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_SEALED > modifier (we could choose to overload ACC_FINAL for bit preservation), and a > Sealed attribute which contains a list of permitted subtypes (similar in > structure to the nestmate attributes.) > Transitivity. Sealing is transitive; unless otherwise specified, an abstract > subtype of a sealed type is implicitly sealed, and a concrete subtype of a > sealed type is implicitly final. This can be reversed by explicitly modifying > the subtype with the non-sealed or non-final modifiers. > Unsealing a subtype in a hierarchy doesn?t undermine the 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 transitivity, requiring > sealing to be opted into at all levels. This is widely believed to be a source > of bugs; it is rare that one actually wants a subtype of a sealed type to not > be sealed. I suspect the reasoning in Scala was, at least partially, the desire > to not make up a new keyword for ?not sealed?. This is understandable, 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: > sealed interface ConstantDesc > permits String, Integer, Float, Long, Double, > ClassDesc, MethodTypeDesc, MethodHandleDesc, > DynamicConstantDesc { } > sealed interface ClassDesc extends ConstantDesc > permits PrimitiveClassDescImpl, ReferenceClassDescImpl { } > private class PrimitiveClassDescImpl implements ClassDesc { } > private class ReferenceClassDescImpl implements ClassDesc { } > sealed interface MethodTypeDesc extends ConstantDesc > permits MethodTypeDescImpl { } > sealed interface MethodHandleDesc extends ConstantDesc > permits DirectMethodHandleDesc, MethodHandleDescImpl { } > sealed interface DirectMethodHandleDesc extends MethodHandleDesc > permits DirectMethodHandleDescImpl > // designed for subclassing > non-sealed class DynamicConstantDesc extends ConstantDesc { ... } > Enforcement. Both the compiler and JVM should enforce sealing. > 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 understand the velocity problem (adding something in the VM is less easier than in the compiler) but by delaying the implementation in the VM, it means you make the code of the VM more complex because there is now 3 states: 1/ do not allow sealed flag on interface, 2/ allow sealed access flag on interface, 3/ verify sealed flag Given that the VM > 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. 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. > Javadoc. The list of permitted subtypes should probably be considered part of > the spec, and 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.) > 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. > ? > Syntactic alternative: Rather than inventing a new modifier (which then needs a > negation modifier), we could generalize the meaning of final , such as: > final class C { } // truly final > final interface Node // explicit version > permits ANode, BNode { } > non-final class ANode implements Node { } // opt-out of transitivity > final interface Node // inferred version > permits { } > This eliminates both sealed and non-sealed as modifiers. The downside is, of > course, that it will engender some ?who moved my cheese? reactions. (We > currently have separate spellings for extends and implements ; some think this > was a mistake, and it surely complicated things when we got to generics, as we > created an asymmetry with . The move of retconning final avoids > replicating this mistake.) This is in line with the suggested retcon at the > classfile level as well. > ? R?mi -------------- next part -------------- An HTML attachment was scrubbed... URL: From brian.goetz at oracle.com Fri Nov 30 15:30:45 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 30 Nov 2018 10:30:45 -0500 Subject: Sealed types In-Reply-To: <1747463018.1229943.1543564739664.JavaMail.zimbra@u-pem.fr> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <1747463018.1229943.1543564739664.JavaMail.zimbra@u-pem.fr> Message-ID: > |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. (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.) > - 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. > ? 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. > > > *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. > 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.) -------------- next part -------------- An HTML attachment was scrubbed... URL: From mark at io7m.com Fri Nov 30 15:52:04 2018 From: mark at io7m.com (Mark Raynsford) Date: Fri, 30 Nov 2018 15:52:04 +0000 Subject: Sealed types In-Reply-To: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> Message-ID: <20181130155204.0f044557@almond.int.arc7.info> On 2018-11-27T17:20:54 -0500 Brian Goetz wrote: > Since we?re already discussing one of the consequences of sealed types, > let?s put the whole story on the table. These are my current thoughts, > but there?s room in the design space to wander a bit. Is the intention to allow this: module M; package x.y.z; __Sealed interface I { } class A implements I {} __Unsealed interface B extends I {} Then, in another compilation unit: module N; package a.b.c; class C implements B {} ... and then: module O; package d.e.f; I x = new C(); switch (x) { case A: ... case B: ... } The switch should be considered exhaustive, because both A and B are direct children of the sealed interface I. However, it's possible for me to add extra subtypes to B because B isn't sealed, and I still get exhaustiveness without mentioning C explicitly. I would expect the following *not* to be exhaustive: module O; package d.e.f; I x = new C(); switch (x) { case A: ... case C: ... } I've mentioned modules and packages explicitly above because it's not clear to me if explicitly unsealed interfaces are permitted to have implementations in different packages or modules without also losing exhaustiveness. -- Mark Raynsford | http://www.io7m.com From brian.goetz at oracle.com Fri Nov 30 16:16:51 2018 From: brian.goetz at oracle.com (Brian Goetz) Date: Fri, 30 Nov 2018 11:16:51 -0500 Subject: Sealed types In-Reply-To: <20181130155204.0f044557@almond.int.arc7.info> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <20181130155204.0f044557@almond.int.arc7.info> Message-ID: > Is the intention to allow this: > > module M; > package x.y.z; > __Sealed interface I { } > class A implements I {} > __Unsealed interface B extends I {} > > Then, in another compilation unit: > > module N; > package a.b.c; > class C implements B {} Yes, unsealing removes all subclassing restrictions. > ... and then: > > module O; > package d.e.f; > I x = new C(); > switch (x) { > case A: ... > case B: ... > } > > The switch should be considered exhaustive, because both A and B are > direct children of the sealed interface I. Yes. > However, it's possible for > me to add extra subtypes to B because B isn't sealed, and I still get > exhaustiveness without mentioning C explicitly. Correct.? A|B is exhaustive over I; if you know about C, you can also say A|C|B, and its still exhaustive. > I would expect the following *not* to be exhaustive: > > module O; > package d.e.f; > I x = new C(); > switch (x) { > case A: ... > case C: ... > } Correct, not exhaustive.? (Allowed by statement switch but not expression switch.) > I've mentioned modules and packages explicitly above because it's not > clear to me if explicitly unsealed interfaces are permitted to have > implementations in different packages or modules without also losing > exhaustiveness. Yes.? As mentioned before, I expect unsealing a _class_ to be more common than unsealing an _interface_, because classes have more tools with which to defend against rogue subtypes. From mark at io7m.com Fri Nov 30 16:53:00 2018 From: mark at io7m.com (Mark Raynsford) Date: Fri, 30 Nov 2018 16:53:00 +0000 Subject: Sealed types In-Reply-To: References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <20181130155204.0f044557@almond.int.arc7.info> Message-ID: <20181130165227.47cf4a2a@almond.int.arc7.info> On 2018-11-30T11:16:51 -0500 Brian Goetz wrote: > > Yes.? As mentioned before, I expect unsealing a _class_ to be more > common than unsealing an _interface_, because classes have more tools > with which to defend against rogue subtypes. > Sounds good, thanks! Do you have any documentation on how Scala's opt-in (not transitive) sealing became a source of bugs? This is news to me, I actually would have expected the opposite. -- Mark Raynsford | http://www.io7m.com From kevinb at google.com Fri Nov 30 20:49:12 2018 From: kevinb at google.com (Kevin Bourrillion) Date: Fri, 30 Nov 2018 12:49:12 -0800 Subject: Sealed types In-Reply-To: <959AF02E-9041-4085-8450-D307EAD4892C@oracle.com> References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <959AF02E-9041-4085-8450-D307EAD4892C@oracle.com> Message-ID: On Thu, Nov 29, 2018 at 5:42 PM Brian Goetz wrote: > I suspect we ought to *recommend* use of `permits` as a kindness to users >> who aren't always looking at javadoc, so they can actually see what to >> switch over. Maybe requiring `permits` always is too much though (it also >> precludes anonymous subtypes, but then again those are of limited value >> anyway, aren't they?). >> > I worry that *requiring *this will feel heavyweight, > Yes, I was musing but I also agree not to require it. The style recommendation should really just be "make sure it's pretty easy for users to know what all the subtypes are - whether that means listing them with `permits` or just keeping them all nearby or whatever." So back to the current state. I'm not 100% following why `permits` > shouldn't just be additive when present. That avoids the "cliff" and we do > generally trust source files to not misuse *themselves*. > > > In nearly all the use cases I have in mind, either the subtypes are > declared all together, or they are strewn about the file system. I have a > harder time imagining the case where you have 20 that are co-declared, and > one that is elsewhere; that?s the case in which the additive interpretation > would pay for itself. Can you think of situations in which this would arise > regularly? > I could run statistics on how commonly classes get subtyped inside vs. outside the package. I could review anomalous cases like "13 within but also 2 outside" to see if it gives us any ideas. (And btw, if not this, please do rack your brain for what other kinds of stats from Our Weird Codebase might be informative for this feature, even if they are just to confirm what we already "know"...) *Transitivity.* Sealing is transitive; unless otherwise specified, an >> abstract subtype of a sealed type is implicitly sealed, and a concrete >> subtype of a sealed type is implicitly final. >> > This can be reversed by explicitly modifying the subtype with the >> non-sealed or non-final modifiers. >> > (FWIW, I found the idea of an unsealed subtype of a sealed supertype > massively confusing for a good while until I finally figured out why > there's nothing wrong with it.) > > An unsealed subCLASS of a sealed super type can be a very useful move (see > DynamicConstnatDesc in JEP-334), but an unsealed subINTERFACE is harder to > grok, because interfaces (absent sealing) lack the ability to constrain > their subclasses very much (no protected members, no final methods, no > nonpublic constructors.) > That's interesting; I had just come to the position that it's fine in either case, because it still means the user knows a total-covering set of subtypes. It's just that they're not leaf types, but callers usually never know/care whether they're dealing the real leaf types anyway. > Do you mean the last statement above as "respectively" (non-sealed can > counteract implicit sealed of abstract; non-final can counteract implicit > final of concrete), or does `non-sealed` also work to counteract the > implicit final of a concrete class? For that matter shouldn't `sealed` > implicit undo the implicit `final` of a concrete class? I admit to still > being fairly confused right now. > > Your confusion is a good argument to consider the syntactic choice of > retconning final, rather than adding sealing :) > Exactly! :-) I hope it pans out. (Syntax: I assume that the syntax is still malleable and not what needs to > be debated here and now. Nevertheless, I don't want to miss my chance to > object to the hyphenation for the record. These won't be seen as two new > keywords, but as a modifier-modifier, and users will not understand when > `non-` works and when it doesn't. I think `unsealed` and `nonfinal` > keywords would be better. `nonfinal` still has its own problems of seeming > more widely applicable than it is?) > > Not to continue this tangent, but just to backpedal a bit on what I said: I've now reviewed all the current modifiers and I see that there aren't really any other very compelling cases where you'd want `non-*` anyway. So, all right I guess. -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Fri Nov 30 22:21:12 2018 From: kevinb at google.com (Kevin Bourrillion) Date: Fri, 30 Nov 2018 14:21:12 -0800 Subject: Sealed types In-Reply-To: References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <959AF02E-9041-4085-8450-D307EAD4892C@oracle.com> Message-ID: On Fri, Nov 30, 2018 at 12:49 PM Kevin Bourrillion wrote: I could run statistics on > And I do remember that there are *already* 3 or so pieces of information you asked for that I haven't worked on. It's still not a bad idea to ask for more - or to reassert which of those things you're still most interested in. My attention is like a ocean tanker, very hard to steer. Hard to steer it onto something, but then hard to steer it off again too :-) -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: From kevinb at google.com Fri Nov 30 22:22:01 2018 From: kevinb at google.com (Kevin Bourrillion) Date: Fri, 30 Nov 2018 14:22:01 -0800 Subject: Sealed types References: <1643c042-6eb9-3ff9-955b-a14152ebfd27@oracle.com> <959AF02E-9041-4085-8450-D307EAD4892C@oracle.com> Message-ID: Sorry amber-spec-experts, that was meant to be a private reply, but I hope it was entertaining. On Fri, Nov 30, 2018 at 2:21 PM Kevin Bourrillion wrote: > On Fri, Nov 30, 2018 at 12:49 PM Kevin Bourrillion > wrote: > > I could run statistics on >> > > And I do remember that there are *already* 3 or so pieces of information > you asked for that I haven't worked on. It's still not a bad idea to ask > for more - or to reassert which of those things you're still most > interested in. My attention is like a ocean tanker, very hard to steer. > Hard to steer it onto something, but then hard to steer it off again too :-) > > > > -- > Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com > -- Kevin Bourrillion | Java Librarian | Google, Inc. | kevinb at google.com -------------- next part -------------- An HTML attachment was scrubbed... URL: