Sealed types

Remi Forax forax at univ-mlv.fr
Fri Dec 7 19:42:29 UTC 2018


> De: "Brian Goetz" <brian.goetz at oracle.com>
> À: "amber-spec-experts" <amber-spec-experts at openjdk.java.net>
> Envoyé: Vendredi 7 Décembre 2018 17:38:53
> Objet: Re: Sealed types

> I’ve updated the document on sealing to reflect the discussion so far.
> Sealed Classes

> Definition. A sealed type is one for which subclassing is restricted according
> to guidance specified with the type’s declaration; finality can be considered a
> degenerate form of sealing, where no subclasses at all are permitted. Sealed
> types are a sensible means of modeling algebraic sum types in a nominal type
> hierarchy; they go nicely with records ( algebraic product types ), though are
> also useful on their own.

> Sealing serves two distinct purposes. The first, and more obvious, is that it
> restricts who can be a subtype. This is largely a declaration-site concern,
> where an API owner wants to defend the integrity of their API. The other is
> that it potentially enables exhaustiveness analysis at the use site when
> switching over sealed types (and possibly other features.) This is less
> obvious, and the benefit is contingent on some other things, but is valuable as
> it enables better compile-time type checking.

> Declaration. We specify that a class is sealed by applying the final modifier to
> a class, abstract class, interface, or record, and specifying a permits list:
> final interface Node
>      permits A, B, C { ... }

> In this explicit form, Node may be extended only by the types enumerated in the
> permits list (which must further be members of the same package or module.)

> In many situations, this may be overly explicit; if all the subtypes are
> declared in the same compilation unit, we may wish to permit a streamlined form
> of the permits clause, that means “may be extended by classes in the same
> compilation unit.”
> final interface Node
>     permits __nestmates { ... }

> (As usual, pseudo-keywords beginning with __ are placeholders to illustrate the
> overall shape of the syntax.)

> We can think of the simpler form as merely inferring the full permits clause
> from information already present in the source file.

> Anonymous subclasses (and lambdas) of a sealed type are prohibited.

I suppose that by anonymous, you mean classes defined in methods named or not. 

Not supporting anonymous classes still seems backward to me. 

By example, this doesn't work: 
final interface Option<T> { 
default T orElseThrow() { throw new NoSuchElementException(); } 

public static <T> Option<T> empty() { return (Option<T>)Empty.EMPTY; } 
public static <T> Option<T> some(T value) { return new Option<>() { public T orElseThrow() { return value; } }; } 
} 
private class Empty<T> implements Option<T> { private static final Empty<?> EMPTY = new Empty<>(); } 

while this works: 
final interface Option<T> { 
default T orElseThrow() { throw new NoSuchElementException(); } 

public static <T> Option<T> empty() { return (Option<T>)Empty.EMPTY; } 
public static <T> Option<T> some(T value) { return new Some<>(value); } 
} 
private class Empty<T> implements Option<T> { private static final Empty<?> EMPTY = new Empty<>(); } 
private class Some<T> implements Option<T> { private final T value; public Some(T value) { this.value = value; } public T orElseThrow() { return value; } } 

basically forcing me to write the code that the compiler generates for me when i use an anonymous class. 

So sorry to be stubborn about that issue but why a stable name is a requirement given that private classes are supported ? 

> Exhaustiveness. One of the benefits of sealing is that the compiler can
> enumerate the permitted subtypes of a sealed type; this in turn lets us perform
> exhaustiveness analysis when switching over patterns involving sealed types.
> (In the simplified form, the compiler computes the permits list by enumerating
> the subtypes in the nest when the nest is declared, since they are in a single
> compilation unit.)

> Note: It is superficially tempting to say permits package or permits module as a
> shorthand, which would allow for a type to be extended by package-mates or
> module-mates without listing them all. However, this would undermine the
> compiler’s ability to reason about exhaustiveness, because packages and modules
> are not always co-compiled. This would achieve the desired subclassing
> restrictions, but not the desired ability to reason about exhaustiveness.

yes ! 

> Classfile. In the classfile, a sealed type is identified with an ACC_FINAL
> accessibility bit, and a PermittedSubtypes attribute which contains a list of
> permitted subtypes (similar in structure to the nestmate attributes.) Classes
> with ACC_FINAL but without PermittedSubtypes behave like traditional final
> classes.

> Sealing is inherited. Unless otherwise specified, abstract subtypes of sealed
> types are implicitly sealed, and concrete subtypes are implicitly final. This
> can be reversed by explicitly modifying the subtype with non-final .

<bikeshed> 
we may re-use 'open' as dual of final here ? 
</bikeshed> 

> Unsealing a subtype in a hierarchy doesn’t undermine all the benefits of
> sealing, because the (possibly inferred) set of explicitly permitted subtypes
> still constitutes a total covering. However, users who know about unsealed
> subtypes can use this information to their benefit (much like we do with
> exceptions today; you can catch FileNotFoundException separately from
> IOException if you want, but don’t have to.)
> Note: Scala made the opposite choice with respect to inheritance, requiring
> sealing to be opted into at all levels. This is widely believed to be a source
> of bugs; it is relatively rare that one actually wants a subtype of a sealed
> type to not be sealed, and in those cases, is best to be explicit. Not
> inheriting would be a simpler rule, but I’d rather not add to the list of
> “things for which Java got the defaults wrong.”

> An example of where explicit unsealing (and private subtypes) is useful can be
> found in the JEP-334 API:
> final interface ConstantDesc
>     permits String, Integer, Float, Long, Double,
>             ClassDesc, MethodTypeDesc, MethodHandleDesc,
>             DynamicConstantDesc { }

> final interface ClassDesc extends ConstantDesc
>     permits PrimitiveClassDescImpl, ReferenceClassDescImpl { }

> private class PrimitiveClassDescImpl implements ClassDesc { }
> private class ReferenceClassDescImpl implements ClassDesc { }
> final interface MethodTypeDesc extends ConstantDesc
>     permits MethodTypeDescImpl { }

> final interface MethodHandleDesc extends ConstantDesc
>     permits DirectMethodHandleDesc, MethodHandleDescImpl { }
> final interface DirectMethodHandleDesc extends MethodHandleDesc
>     permits DirectMethodHandleDescImpl { }

> // designed for subclassing
> non-final class DynamicConstantDesc extends ConstantDesc { ... }

> Enforcement. Both the compiler and JVM should enforce sealing, as they both
> enforce finality today (though from a project-management standpoint, it might
> be allowable for VM support to follow in a later version, rather than delaying
> the feature entirely.)

> Accessibility. Subtypes need not be as accessible as the sealed parent. In this
> case, some clients are not going to get the chance to exhaustively switch over
> them; they’ll have to make these switches exhaustive with a default clause or
> other total pattern. When compiling a switch over such a sealed type, the
> compiler can provide a useful error message (“I know this is a sealed type, but
> I can’t provide full exhaustiveness checking here because you can’t see all the
> subtypes, so you still need a default.”)

> Javadoc. The list of permitted subtypes should be incorporated into the Javadoc.
> Note that this is not exactly the same as the current “All implementing
> classes” list that Javadoc currently includes, so a list like “All permitted
> subtypes” might be added (possibly with some indication if the subtype is less
> accessible than the parent, or including an annotation that there exist others
> that are not listed.)

> Open question: With the advent of records, which allow us to define classes in a
> single line, the “one class per file” rule starts to seem both a little silly,
> and constrain the user’s ability to put related definitions together (which may
> be more readable) while exporting a flat namespace in the public API. I think
> it is worth considering relaxing this rule to permit for sealed classes, say:
> allowing public auxilliary subtypes of the primary type, if the primary type is
> public and sealed.

i think we should relax the rule for any hierarchy not only the sealed one. 

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


More information about the amber-spec-experts mailing list