[swift-evolution] [Proposal] Sealed classes by default
rjmccall at apple.com
Thu Jun 30 13:15:03 CDT 2016
> On Jun 30, 2016, at 11:07 AM, Andrew Trick <atrick at apple.com> wrote:
>> On Jun 30, 2016, at 1:21 AM, John McCall via swift-evolution <swift-evolution at swift.org> wrote:
>>> On Jun 29, 2016, at 10:17 PM, L. Mihalkovic <laurent.mihalkovic at gmail.com> wrote:
>>>> On Jun 29, 2016, at 8:39 PM, Vladimir.S via swift-evolution <swift-evolution at swift.org> wrote:
>>>> How about `public(extensible)` ?
>>>> On 29.06.2016 21:32, John McCall via swift-evolution wrote:
>>>>>> On Jun 29, 2016, at 11:16 AM, Michael Peternell via swift-evolution <swift-evolution at swift.org> wrote:
>>>>>> Do you mean `public(unsealed)`? Because `internal(unsealed)` doesn't really make sense. `internal` declarations are always sealed.
>>>>> If "sealed" is the default behavior for public classes and methods — and I don't think the modifier is worth adding unless it's the default
>>> What a jump... I am very curious about this rational that seems so obvious to you?
>> There are a thousand different ways to add minor variations on language features. In almost every single thread of any length on this list, you can find someone who got attached to a slightly different feature from the one that eventually got picked, and often you can find them asking why we didn't just add that, too, maybe under a different name or using different syntax. If we added every single one of those, the language would not be better, it would just be absurdly, dauntingly complex. We have to look at these kinds of additive features with a very critical eye and decide how much code will really benefit from it.
>> In our experience, idiomatic Swift libraries don't center that much around classes, and especially not around class inheritance. Value types are usually a better and more efficient data representation than juggling shared mutable state, and protocols are a cleaner and much more robust tool for polymorphism than overriding methods. Classes are useful for modeling values with "identity", but even then, the class itself can often be final: when its operations need to be polymorphic, it's usually better to just pass in a closure or an existential, because piling up overrides turns code into spaghetti very quickly.
>> That doesn't mean we don't want to add good tools for classes, of course, but inheritance and overriding have a lot of subtle interactions, both at a low level with other language features and at a high level with any attempt to establish basic object invariants. Programmers want an endless variety of language tools for ensuring that overrides satisfy the contract correctly — "require the super method to be called", "require the super method to be called in exactly this way", "require the super method to be called unless the method exits early", "only allow calls to these specific other methods", "require this method to be called before doing anything else", ad infinitum. The only way we could possibly support all that is by first adding some massive new contracts feature and then embellishing it with a ton of inheritance-specific logic; in other words, everything with classes quickly balloons into a a huge research project, because (in my opinion) overriding is just an inherently clumsy tool.
>> So when you look at something like 'sealed' as an opt-in modifier, you have to think carefully about who is actually going to use it. It's an explicit modifier that limits inheritance, but we already have 'final', so it's only useful when the library wants to provide its own private subclasses, but doesn't want to commit to full public inheritability. How often does that happen, really? Are we adding this feature for a handful of developers in the entire world, most of whom could probably find another solution to their problem? And that's for 'sealed' on a class — are we really supposed to expect that someone will unseal a class, but individually protect all the methods and properties they don't want overridden?
>> In contrast, 'sealed' as a default is a conservative protection against forgetting to add 'final' (which will quickly get caught if the class is truly meant to be subclassed), and applied method-by-method it encourages the developer to think carefully about where they want the extension points to be on their class. Moreover, it enables a number of interesting language enhancements which can only be done safely with complete knowledge of the inheritance tree, and it significantly improves performance without adding any invasive annotations. And because it's already there as the default behavior, that handful of developers who actually need it specifically can get it for free.
> 100% agree. Sealed-by-default is the only thing that makes sense to me at module boundaries and would obsolete the use of final-like keywords as magical performance tools. I was dismayed to see “final” added out of performance necessity and would love to see it return to a pure role as an API constraint.
> To me, module is about what parts of the system can be compiled independently and support binary distribution. That’s being conflated with source distribution, but it should be possible to separate those concerns.
Agreed. Where we do have source distributions of modules, we should of course take advantage of the fact that we're compiling things together, but ultimately we want binary modules to be as efficient as possible.
More information about the swift-evolution