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
More information about the amber-spec-observers
mailing list