> On 21 Jul 2016, at 03:41, Brent Royal-Gordon <[email protected]> wrote:
> 
>> On Jul 20, 2016, at 3:26 PM, Karl <[email protected]> wrote:
>> 
>>> Sealed is *non-committal*. It makes no promises to wider scopes about 
>>> whether there are other subclasses/overrides; it merely states that code 
>>> outside the module may not subclass/override. `final`, on the other hand, 
>>> is an *affirmative* statement: there are no subclasses/overrides and there 
>>> never will be. Code outside the module is permitted to rely on that 
>>> fact—for instance, it can generate static calls and conflate dynamic Self 
>>> with static Self in conformances.
>> 
>> This is exactly what I'm talking about - this is actually a very simple 
>> discussion. Throwing around words like “non-committal” and “affirmative” and 
>> speaking abstractly doesn’t disguise the brunt of what you’re saying: 
>> limiting “open” to public classes only lets you be sloppy inside your own 
>> module. That’s the only reason to make it like that. If I were describing 
>> the concept of “sloppiness” while trying my hardest not to use the word 
>> itself, I would probably say pretty much what you just wrote - wanting to 
>> remain non-committal, avoid definite, affirmative statements, etc.
> 
> No, that is *not* the only reason. The other reason is library evolution.
> 
> A class that is closed in 1.0 can be opened in 1.1; a class that is `final` 
> in 1.0 *cannot* be opened in 1.1 (or at least it's a breaking change if it 
> is). This is a really important distinction for binary compatibility—but 
> binary compatibility only matters at the public boundary. An 
> `internal`-or-less class does not have to worry about maintaining a 
> compatible binary interface between different versions, but a `public` class 
> does.
> 
> When I say it's "non-commital", that's what I mean: It does not commit future 
> versions of the module to either permit or forbid subclasses. A future 
> version can either make it `open` or `final` as the module's evolving design 
> demands. A `final` class must remain `final` forever. It's the difference 
> between a door that's locked and a door that's bricked up.
> 
> Yes, it is *also* true that sealed-by-default permits sloppiness as long as 
> it's contained to internal scope. But that's quite typical of Swift. It's why 
> the default access control is `internal`, not `private` (the minimally-sloppy 
> solution) or `public` (the minimally-bureaucratic solution). But these are 
> two independent arguments for sealed. Railing against internal sloppiness 
> doesn't change the library evolution argument.
> 
> -- 
> Brent Royal-Gordon
> Architechies
> 

I would be okay with the inferred situation for classes being a semantic 
“final”. That is, that they cannot be subclassed, but they won’t be 
automatically optimised in a fragile way either. We could call it “sealed” if 
you want to explicitly specify it - the difference is that it doesn’t only 
apply at the module boundary, and it’s just an annotation for the external 
modules’ type-checker that it shouldn’t allow this - it’s not license for the 
internal module’s compiler to give up flexibility. So:

public class Foo {}                        // Implicitly “sealed”. Cannot be 
subclassed anywhere. Does not provide optimiser guarantees of “final”.
public(sealed) class Foo {}           // as above

public final class Foo {}                // Implicitly “sealed”. Cannot be 
subclassed anywhere. Allows resilience-breaking optimisations.
public(sealed) final class Foo {}   // as above

public internal(open) class Foo {}                       // “open” overrides 
“sealed” for the internal scope. Cannot be subclassed externally; may be 
subclassed internally. Does not provide optimiser guarantees of “final”.
public(sealed) internal(open) class Foo {}          // as above

public(sealed) internal(open) final class Foo {}  // Error: A class cannot be 
both open and final

I believe that would meet the goals of:

- Not allowing subclassing from external modules unless explicitly allowed (the 
original goal)
- Making classes which are internally-subclassed easier to locally reason about 
(my nice-to-have)
- Maintain binary compatibility
- Do not give up binary flexibility unless the user explicitly asks for it (in 
the LibraryEvolution docs)

Is there anything I missed?

Karl
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to