> 
> 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 <bikeshed> { }
> 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 <T  extends U>. 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. 



Reply via email to