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 <brian.go...@oracle.com> wrote:
> 
> The most obvious remaining question appears to be: how do we spell "permits 
> <nest-members>".  
> 
> 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.
>> 
> 

Reply via email to