Sealed types

Brian Goetz brian.goetz at oracle.com
Fri Dec 7 18:57:07 UTC 2018


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 <brian.goetz at oracle.com 
>> <mailto:brian.goetz at 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.
>>>
>>>>>
>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20181207/32ef5f13/attachment-0001.html>


More information about the amber-spec-experts mailing list