> On Dec 7, 2015, at 20:30 , John McCall via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
>> On Dec 7, 2015, at 7:18 PM, Matthew Johnson via swift-evolution 
>> <swift-evolution@swift.org> wrote:
>>> Defaults of public sealed/final classes and final methods on a class by 
>>> default are a tougher call. Either way you may have design issues go 
>>> unnoticed until someone needs to subclass to get the behavior they want. So 
>>> when you reach that point, should the system error on the side of rigid 
>>> safety or dangerous flexibility?
>> 
>> This is a nice summary of the tradeoff.  I strongly prefer safety myself and 
>> I believe the preference for safety fits well with the overall direction of 
>> Swift.  If a library author discovers a design oversight and later decides 
>> they should have allowed for additional flexibility it is straightforward to 
>> allow for this without breaking existing client code.  
>> 
>> Many of the examples cited in argument against final by default have to do 
>> with working around library or framework bugs.  I understand the motivation 
>> to preserve this flexibility bur don't believe bug workarounds are a good 
>> way to make language design decisions. I also believe use of subclasses and 
>> overrides in ways the library author may not have intended to is a fragile 
>> technique that is likely to eventually cause as many problems as it solves.  
>> I have been programming a long time and have never run into a case where 
>> this technique was the only way or even the best way to accomplish the task 
>> at hand.
>> 
>> One additional motivation for making final the default that has not been 
>> discussed yet is the drive towards making Swift a protocol oriented 
>> language.  IMO protocols should be the first tool considered when dynamic 
>> polymorphism is necessary.  Inheritance should be reserved for cases where 
>> other approaches won't work (and we should seek to reduce the number of 
>> problems where that is the case).  Making final the default for classes and 
>> methods would provide a subtle (or maybe not so subtle) hint in this 
>> direction.
>> 
>> I know the Swift team at Apple put a lot of thought into the defaults in 
>> Swift.  I agree with most of them. Enabling subclassing and overriding by 
>> default is the one case where I think a significant mistake was made.
> 
> Our current intent is that public subclassing and overriding will be locked 
> down by default, but internal subclassing and overriding will not be.  I 
> believe that this strikes the right balance, and moreover that it is 
> consistent with the general language approach to code evolution, which is to 
> promote “consequence-free” rapid development by:
> 
>  (1) avoiding artificial bookkeeping obstacles while you’re hacking up the 
> initial implementation of a module, but
> 
>  (2) not letting that initial implementation make implicit source and binary 
> compatibility promises to code outside of the module and
> 
>  (3) providing good language tools for incrementally building those initial 
> prototype interfaces into stronger internal abstractions.
> 
> All the hard limitations in the defaults are tied to the module boundary 
> because we assume that it’s straightforward to fix any problems within the 
> module if/when you decided you made a mistake earlier.
> 
> So, okay, a class is subclassable by default, and it wasn’t really designed 
> for that, and now there are subclasses in the module which are causing 
> problems.  As long as nobody's changed the default (which they could have 
> done carelessly in either case, but are much less likely to do if it’s only 
> necessary to make an external subclass), all of those subclasses will still 
> be within the module, and you still have free rein to correct that initial 
> design mistake.

I think John summarized my position very well, so of course I'm going to come 
in here and add more stuff. :-)

In working on the design for library evolution support ("resilience"), we've 
come across a number of cases of "should a library author be able to change 
this when they release v2 of their library?" Many times, the answer is it's 
possible to do something in one direction, but not at all safe to go the other 
way. For example, you can always add public methods to a class, but you can't 
remove public methods because you don't know who's calling them. You can mark 
them deprecated, but that doesn't help with any client apps that have already 
been compiled and shipped.

One of the things that came up was "can you add 'final' to a class?" And of 
course you can't, because you don't know who may have already subclassed it. 
That's very unfortunate for a library author who simply forgot to add 'final' 
when they were first writing the class.

The interesting thing about this is that the "error of omission"—of failing to 
think about whether a class should be final—is worse than the alternative. 
Ignoring optimizations for a minute, a class that starts out 'final' can 
certainly become non-final later; it doesn't change how the class is currently 
used.* For a lot of library evolution questions, this is the preferred answer: 
the default should be safe, and the designer of the class can choose to be more 
aggressive later.

This is also the guiding principle behind the behavior of 'public'. A number of 
people have asked for members of a public struct to implicitly be made public. 
But here again the "error of omission" is problematic: a helper function you 
add for your own use may now be depended on by client apps far and wide, just 
because you forgot to customize the access control. So Swift says you should 
explicitly consider the public interface of every type.

Why 'sealed' instead of 'final' as the default? Because inheritance is useful, 
and within your own code having to opt into it starts to feel like unnecessary 
clutter. This is a trade-off, just like defaulting to 'internal' over 
'private', but it's one that keeps life easy for a single developer with a 
single module: their app. (And the compiler can still do useful things with 
non-final classes if it can see the entire class hierarchy.) Additionally, 
limiting inheritance to the current file (a la 'private') is also potentially 
useful.

This direction separates "limiting inheritance/overrides" from "has no 
subclasses/overrides". The former is about defining the limits of your API; the 
latter is a promise that can be used for performance. I think that's a good 
thing.

Jordan

* Even without optimizations a 'final' class cannot safely drop the 'final'. If 
a class is 'final', it may have additional 'required' initializers added in 
extensions.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to