[swift-evolution] Final by default for classes and methods

Jordan Rose jordan_rose at apple.com
Mon Dec 21 13:50:12 CST 2015


Okay, so I probably shouldn't be putting this so bluntly, but the ship has already sailed on this. Supporting arbitrary code injection into someone else's framework is a non-goal for Swift, perhaps even an anti-goal.

- 'private' and 'internal' methods are not exposed outside of a library, so you can't call them, much less override them. Similar for 'private' and 'internal' classes: you cannot subclass them.

- Structs, enums, protocol extensions, and free functions are all not overrideable. (Similarly, neither are C functions or pretty much anything C++.)

- There's a difference between "we're not going to optimize" and "we're not going to optimize now". Objective-C's "everything uses objc_msgSend" model is essentially unoptimizable. It's not that the developer can't work around that when performance is necessary; it's that the resulting code doesn't feel like Objective-C. Swift can do better, and even with its current semantics it does do better, for free. (And optimizations in frameworks are incredibly important. Where do you think your app spends most of its CPU time? I would guess for many many non-game apps, it's in framework code.)

- A major goal of Swift is safety. If you are writing a safe type built on unsafe constructs (like, say, Array), it is imperative that you have some control over your class invariants to guarantee safety. At the same time, your clients shouldn't have to know that you're built on unsafe constructs.

That last one is really the most important one. If you replace a method on someone else's class, you don't actually know what semantics they're relying on. Of course Apple code will have bugs in it. Trying to patch over these bugs in your own code is (1) obviously not an answer Apple would support, but also (2) fraught with peril, and (3) likely to break in the next OS release.

TLDR: It's already unsafe to do this with the existing set of Swift features. Yes, this makes things "worse", but it's not something we're interested in supporting anyway.

Jordan


> On Dec 19, 2015, at 20:02 , Rod Brown <rodney.brown6 at icloud.com> wrote:
> 
> Yeah, this really is a difficult one.
> 
> Adding final seems to be a risky thing at all, from a library or framework standpoint. As you say, there is hubris there in the suggestion “assume we’re right, and you can’t work around us.” I can see developer relations being inundated and unable to provide effective workarounds when the assumption is closed frameworks and finalised subclassing.
> 
> If they allow final in public frameworks, the default of “sealed” as mentioned by Jordan Rose makes sense. Closing an API further at a later date creates hell for those who subclass classes “hoping” that classes don’t become “finalised”. How do you handle such cases? It’s better than the alternatives.
> 
> But that suggests that indeed there is a greater problem here. Final in end products makes sense. It provides clarity, and allows optimisations. But in frameworks? For those who rely on the frameworks, the ability to subclass to avoid a bug, or to add functionality, while unintended and potentially unsafe, is something we use to develop apps, and how we inspire development of the framework. I think this risks stifling creativity and blocking effective workarounds to bugs.
> 
> If we are to add finalisation to API for frameworks, it makes sense to do “Sealed” by default as discussed. But perhaps it needs to be examined if we really want this aggressive optimisation and restriction on frameworks at all.
> 
> 
> 
>> On 20 Dec 2015, at 2:09 PM, Curt Clifton via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>> I'm not sure how many minuses I have to give, but I'd give them all to this proposal.
>> 
>> Anyone who tries to ship products on release day of Apple's operating system updates spends most of August and September writing horrible hacks so that their users are insulated from OS bugs as much as possible. All software has bugs, frameworks included. Please don't take away our tools for working around those bugs. Making classes final by default assumes a level of perfection on the part of framework developers that is not achievable.
>> 
>> Yes, subclassing a class that wasn't designed to be subclassed has serious risks. Thoughtful developers sometimes take on those risks in order to serve their customers.
>> 
>> Frankly, I think having `final` in the language at all is a mistake. While I agree that we should prefer composition to inheritance*, declaring things final is hubris. The only reasonable use case I've seen is for optimization, but that smacks of developers serving the compiler rather than the converse. Bringing an analog of NS_REQUIRES_SUPER to Swift would be most welcome; that's as far as I'd go down the path of dictating framework usage.
>> 
>> Cheers,
>> 
>> Curt
>> 
>> *- and am thrilled with the property behaviors proposal for this use case
>> 
>> 
>> On Dec 17, 2015, at 5:55 PM, Joe Groff via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> 
>>> 
>>>> On Dec 17, 2015, at 5:41 PM, Rod Brown via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>> 
>>>> My opinion is -1 on this proposal. Classes seem by design to intrinsically support subclassing.
>>>> 
>>>> What if one framework provider thinks “you won’t need to subclass this ever” but didn’t realise your use case for doing so, and didn’t add the keyword? When multiple developers come at things from different angles, the invariable situation ends with use cases each didn’t realise. Allowing subclassing by default seems to mitigate this risk at least for the most part.
>>> 
>>> Frameworks change, and if the framework author didn't anticipate your use case for subclassing, they almost certainly aren't going to anticipate it while evolving their implementation and will likely break your code. Robust subclassability requires conscious design just like all other aspects of API design.
>>> 
>>> -Joe
>>> 
>>>> I think this definitely comes under the banner of “this would be nice” without realising the fact you’d be shooting yourself in the foot when someone doesn’t add the keyword in other frameworks and you’re annoyed you can’t add it.
>>>> 
>>>> 
>>>>> On 18 Dec 2015, at 10:46 AM, Javier Soto via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>> 
>>>>> Does it seem like there's enough interesest in this proposal? If so, what would be the next steps? Should I go ahead and create a PR on the evolution repo, describing the proposal version that Joe suggested, with classes closed for inheritance by default outside of a module?
>>>>> 
>>>>> Thanks!
>>>>> 
>>>>> On Tue, Dec 8, 2015 at 7:40 AM Matthew Johnson via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>>> I understand the rationale, I just disagree with it.
>>>>> 
>>>>> IMO adding a keyword to state your intention for inheritance is not a significant obstacle to prototyping and is not artificial bookkeeping.  I really don't understand how this would conflict with "consequence-free" rapid development.  It is a good thing to require people to stop and think before using inheritance.  Often there is a more appropriate alternative.
>>>>> 
>>>>> The assumption that it is straightforward to fix problems within a module if you later decide you made a mistake is true in some respects but not in others.  It is not uncommon for apps to be monolithic rather than being well factored into separate modules, with many developers contributing and the team changing over time.  While this is not ideal it is reality.
>>>>> 
>>>>> When you have the full source it is certainly *possible* to solve any problem but it is often not straightforward at all.  Here is an example of a real-work scenario app developers might walk into:
>>>>> 
>>>>> 1) A class is developed without subclassing in mind by one developer.
>>>>> 2) After the original developer is gone another developer adds some subclasses without stopping to think about whether the original developer designed for subclassing, thereby introducing subtle bugs into the app.
>>>>> 3) After the second developer is gone the bugs are discovered, but by this time there are nontrivial dependencies on the subclasses.
>>>>> 4) A third developer who probably has little or no context for the decisions made by previous developers is tasked with fixing the bugs.
>>>>> 
>>>>> This can be quite a knot to untangle, especially if there are problems modifying the superclass to properly support the subclasses (maybe this breaks the contract the superclass has with its original clients).
>>>>> 
>>>>> It may have been possible to avoid the whole mess if the second developer was required to add 'inheritable' and 'overrideable' keywords or similar.  They are already required to revisit the source of it while adding the keywords which may lead to consideration of whether the implementation is sufficient to support inheritance in their currently intended manner.
>>>>> 
>>>>> Implementation inheritance is a blunt tool that often leads to unanticipated problems.  IMO a modern language should steer developers away from it and strive to reduce the cases where it is necessary or more convenient.  Making final the default would help to do this.
>>>>> 
>>>>> Supporting sealed classes and methods that can only be subclassed or overridden within the same module is not in conflict with final by default.  Both are good ideas IMO and I would like to see both in Swift.
>>>>> 
>>>>> I hope the core team is willing to revisit this decision with community input.  If not I will let it go, although I doubt I will ever agree with the current decision.
>>>>> 
>>>>> Matthew
>>>>> 
>>>>> Sent from my iPad
>>>>> 
>>>>> On Dec 7, 2015, at 10:30 PM, John McCall <rjmccall at apple.com <mailto:rjmccall at apple.com>> wrote:
>>>>> 
>>>>> >>> On Dec 7, 2015, at 7:18 PM, Matthew Johnson via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at 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.
>>>>> >
>>>>> > John.
>>>>> _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>>> -- 
>>>>> Javier Soto  _______________________________________________
>>>>> swift-evolution mailing list
>>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>>> 
>>>>  _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151221/6dc4970e/attachment-0001.html>


More information about the swift-evolution mailing list