[swift-evolution] [swift-evolution-announce] [Review #2] SE-0117: Default classes to be non-subclassable publicly
brent at architechies.com
Wed Jul 20 04:07:42 CDT 2016
> On Jul 19, 2016, at 6:14 PM, Karl <razielim at gmail.com> wrote:
> That is to say, we basically introduce “open" as an inverted “final”, and make all classes non-open by default. That is analogous to enabling whole-module-optimisation by default, in my opinion. The cost of an extra four-letter word isn’t that great, but the benefits to readability and reasonability all-around make it worthwhile.
Okay, but this ignores an important difference between sealed and final.
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.
But this distinction only really makes sense at the `public` boundary, because that's where the compiler—and even the developer—faces an impenetrable encapsulation barrier. If you want to require `open` even on non-public declarations, you thus need to choose one of these designs:
1. `final` is still available, but only on public APIs.
2. All non-public classes have to be explicitly declared either `open` or `final`.
3. Sealed has some sort of complex, scope-specific design (for instance, an `internal` class can be subclassed in `fileprivate` but not in `internal` scope).
Of these three, I think that #2 is overly bureaucratic and #3 is overly complicated, so only #1 is a viable option. And then we're just choosing whether we have internal subclassing by default and an odd public-only `open` keyword, or no internal subclassing by default and an odd public-only `final` keyword. No viable option avoids some kind of asymmetry at the `public` boundary.
Open-by-default with a `final` keyword is the traditional design. It is simpler for people learning to program and more familiar for people new to Swift. It is more convenient. And you just don't need the same rigorous, formal definition of semantics in internal scope, where you can alter a problematic invariant rather than having to live with it.
All that said, there most likely *is* value in declaring, even internally, which classes are meant to be subclassed and which members are meant to be overridden. Perhaps what we ought to do is permit `open` even on non-public APIs, but not enforce it (or rather, leave enforcement to linters). That would allow people and teams to explicitly document internal overriding behavior if they want to, without burdening the teams that don't want to go to the effort.
More information about the swift-evolution