[Review] SE-0117: Default classes to be non-subclassable publicly

The justification for this proposal is all about supporting the people who are working to design library APIs right, and about maintaining consistency with the design philosophy of Swift. To wit: in Swift, where there’s a default choice, it’s the safe one;

I challenge this claim:
Safety is valued, but Swift cares (and should care!) about pragmatism as well… the most obvious example that comes to my mind are arrays, which have no safeguards that stop you from accessing elements that aren't there.

where there’s a consequential design decision, it’s explicit.

When there is no explicit statement about subclassiblily, it's reasonable to assume that there hasn't been a consequential design decision… but sadly, discussion like this mainly driven by dogmatism,

Hence why it is important to recognise this, whether it is us doing it or others, and nip it in the bud. I do not want orthodoxy wars with people yelling at each other about who is the truest Swiftiest supporter of the one true Swift dogma :P.

···

Sent from my iPhone

On 11 Jul 2016, at 09:42, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

because there is no evidence for either side.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Here, I also disagree. Imagine we are talking about an open-source library on GitHub. People will complain about the lack of sub-classability through issues and pull-requests. This will hopefully be enough to get discussions going on what is wrong with the API to warrant subclassing and exactly what the subclassing extension points should be. This discussion will happen around many libraries and will help educate people to think carefully about subclassing.

That's one of the false assumptions:
Many people don't want be lectured, but simply get their work done… as it has been said before, this list is a quite exotic bubble, and it can be fatal to assume that it is representative for the mass of developers that have to deal with the consequences of its discussions.

Hence why it is important to recognise this, whether it is us doing it or others, and nip it in the bud. I do not want orthodoxy wars

there has been war for a long time — but it's fought with weapons that are hard to recognize ;-) (no, actually :-(

The justification for this proposal is all about supporting the people who are working to design library APIs right, and about maintaining consistency with the design philosophy of Swift. To wit: in Swift, where there’s a default choice, it’s the safe one;

I challenge this claim:
Safety is valued, but Swift cares (and should care!) about pragmatism as well… the most obvious example that comes to my mind are arrays, which have no safeguards that stop you from accessing elements that aren't there.

I'm not sure I understand this point. First, a guaranteed runtime trap on an out-of-bounds index is a *massive* safety improvement over what we had in C/C++/Objective-C.
Second, safety is explicitly goal #1 for Swift; it's even more important than performance and expressiveness (although fortunately these aren't always at odds).

where there’s a consequential design decision, it’s explicit.

When there is no explicit statement about subclassiblily, it's reasonable to assume that there hasn't been a consequential design decision… but sadly, discussion like this mainly driven by dogmatism, because there is no evidence for either side.

I've made some notes of the arguments (pro and con) that I've seen in this thread and elsewhere; see them below. I think we have seen ample evidence in support for most of them, from both sides. As usual, our differences are difficult/impossible to reconcile. People may have become a bit theatrical at times, but the discussions I've read have been far from dogmatic.

Thankfully we can rely on the core team to break the stalemate. (I think it's safe to say that despite all the drama, things will turn out OK either way. Things generally do.)

I'm firmly in the +1 camp, so take my summary of the arguments against the proposal with a grain of salt. For what it's worth, I am sympathetic to most of the -1 arguments; I just find them less convincing in contrast. Some of the labels are somewhat bombastic; sorry about that.

## Arguments for SE-0117

- Doctrine: Safety is the most important design goal of Swift, and having to opt-in to cross-module subclassing has been demonstrated to be the safer choice.

- Authority: We've seen that in some OOP communities, "Explicitly design for subclassing or else prohibit it" has been a well-known and highly regarded API design advice for decades.

- Precedent: Kotlin's designers made the (admittedly questionable) decision of making final classes the default; the world didn't end. (And SE-0117 proposes a much better approach than that.) Moreover, Rust and Go are examples of popular languages that have decided to completely get rid of (implementation) inheritance; again, the sky did not fall on the heads of their designers: these languages have grown quite popular regardless of this decision. (Note though that nobody is suggesting to remove subclassing from Swift.)

- Convenience: For small-scale library developers, it becomes a little easier to create and maintain well-designed reusable Swift modules, by not having to remember to disable external subclassing whenever a new public class is created. Large-scale framework developers like Apple will remain able to do whatever they want, regardless of this proposal.

- Sensible Defaults: Accidentally leaving a class sealed can be easily fixed in a point release; accidentally leaving a class open can only be fixed by a major API version bump, because closing a class may break existing client code. (Provided that you care about strict API versioning.) So it makes sense to make the less damaging choice the default.

- Design Variety: Allowing sealed classes (either opt-in or opt-out) makes it possible to make class-hierarchies public without the burden of preparing for external subclasses. This makes certain API design patterns easier to implement in Swift. Ironically, introducing sealed classes may thus make subclassing *more* popular in carefully designed Swift packages.

- Performance: Sealing classes by default may enable the compiler to eliminate dynamic dispatch in more cases where allowing overrides was never intended. This may sometimes lead to meaningfully faster code.

- Limited Scope: Because the proposal only restricts subclassing across module boundaries, and because Swift already has a clear bias towards value types and protocol-oriented programming, and because final is already in widespread use in the language, many or most Swift users would not actually be significantly affected by this change.

- Education: The proposal gently encourages Swift users to think about ways other than subclassing to solve problems. Previous experience with the complexity of subclassing has lead to it gaining something of a bad reputation. This has made it unfashionable in the trendier section of the programming community, who are always very enthusiastic to offer advice about better approaches, and some of whom aren't even shy about showing their monads in public.

- Auditing: Some coding conventions discourage the use of subclassing in public APIs. Modules that expose APIs where the user *needs* to subclass would become easier to spot if this proposal was accepted. Programmers who're particularly pedantic and/or battle-scarred would be able to easily spot open classes and treat them as an obvious code smell.

## Arguments against SE-0117

- Pragmatism: Working around bugs by monkey patching closed-source libraries becomes even harder to pull off than it already is. An ecosystem of carefully designed and bug-free modules is a pipe dream; in the real world, packages are quickly hacked together with little if any consideration to concerns of the ivory tower people. My dependencies are full of bugs that upstream authors cannot or will not fix in time for my next release.

- Extendability: Subclassing is an important way to extend third-party classes with new functionality, and this proposal would often remove people's ability to do this. Library authors will never be able to predict all the ways their code will need to be extended. Some important classes of functionality cannot easily be added via class extensions and/or composition. Library authors should expect no right to restrict the ways their library may or may not be used and extended.

- Senseless Defaults: The design as proposed arguably provides a slight benefit to authors of well-designed libraries, but it will make subpar libraries *much* worse. Lazy library authors will leave subclassing disabled even though they might not object against subclassing at all. This will lead to a plague of one-liner pull requests to open up classes.

- Tradition: Mainstream 80s/90s-era OOP languages invariably leave classes open by default. Newcomers would be shocked to find that Swift is doing otherwise. The proposal introduces a frustrating quirk into the language with at best marginal benefits in exchange.

- Annoyance: People will need to remember to manually add an extra keyword to their public classes whenever they want to allow subclassing. People who want to always allow cross-module subclassing will always have to add the keyword. The extra work to do this would be annoying and pointless, and the proliferation of such additional keywords would make Swift needlessly wordy.

- Unit testing: The common (if messy) Objective-C / Java approach to mocking an external class by subclassing it and overriding every method becomes even more impossible in Swift. (I can't recall anyone actually making this argument, but it would've been an interesting point.)

- Incompleteness: This proposal does not go far enough; if we prefer to go in this direction, nothing less than requiring a formal description of the subclass-superclass API will do. Enabling module-external subclassing by the simple addition of a keyword would be irresponsible.

- Slippery Slope: SE-0117 adds yet another entry to the already huge list of things in Swift that subtly or openly discourage people from subclassing. How far are we from someone seriously proposing to outright rip inheritance out of the language? Enough is enough. Stop with the anti-subclassing propaganda. Implementation inheritance is a hugely important core language feature whose popularity should be preserved and whose use should be encouraged and celebrated.

- Heresy: Some people don't like programming languages that take away their freedom to do however they please. People should be trusted to use their tools well; even if they're slightly dangerous. Freedom over bureaucracy!

- The Weirdo Argument: This list represents a very curious subset of Swift developers, who are very different to the vast majority of people using Swift. Normal people just want to get their work done, and they would be infuriated if such a change would be implemented. This list is a quite exotic bubble, and it can be fatal to assume that it is representative for the mass of developers that have to deal with the consequences of its discussions. Programming languages are best evolved in a strictly democratic manner.

* * *

Sorry if I left something off, or if I misrepresented an argument. (This is a huge thread.) I had some trouble making sense of the last two or three arguments, so they're especially unlikely to represent people's actual opinions.

···

On 2016-07-11 08:42:33 +0000, Tino Heth via swift-evolution said:

--
Károly
@lorentey

- Slippery Slope: SE-0117 adds yet another entry to the already huge list of things in Swift that subtly or openly discourage people from subclassing. How far are we from someone seriously proposing to outright rip inheritance out of the language? Enough is enough. Stop with the anti-subclassing propaganda. Implementation inheritance is a hugely important core language feature whose popularity should be preserved and whose use should be encouraged and celebrated.

This is another reason I’m unclear on the reasoning behind this proposal, but I could be missing something… Structs have been pushed in Swift primarily as classes without polymorphism. One would think that one of the primary reasons to adopt a class structure in Swift is polymorphism. It seems backwards to make the point of a class primarily polymorphism, and then disable it by default.

I suppose you could make the case you’d want to inherit from a parent class but not allow other classes to inherit from you. This just seems like more of a mess though. I could have vendors shipping me view controllers that I can’t inherit from, complicating my own designs.

(The other big reason I still use a lot of classes in Swift is Cocoa compatibility, but I’m assuming Obj-C compatible Swift objects won’t support final or final-by-default anyway.)

Someone correct me if I’m missing something big here though.

- Slippery Slope: SE-0117 adds yet another entry to the already huge list of things in Swift that subtly or openly discourage people from subclassing. How far are we from someone seriously proposing to outright rip inheritance out of the language? Enough is enough. Stop with the anti-subclassing propaganda. Implementation inheritance is a hugely important core language feature whose popularity should be preserved and whose use should be encouraged and celebrated.

This is another reason I’m unclear on the reasoning behind this proposal, but I could be missing something… Structs have been pushed in Swift primarily as classes without polymorphism. One would think that one of the primary reasons to adopt a class structure in Swift is polymorphism. It seems backwards to make the point of a class primarily polymorphism, and then disable it by default.

Classes are the only way to create a reference type in Swift. In idiomatic Swift code, classes are frequently employed for this exact feature alone (to e.g. implement copy-on-write storage).

Subtype polymorphism is an additional feature that is (while obviously important and useful elsewhere) frequently unwanted in this context. One can easily imagine a bizarro alternate universe where Swift classes do not support subclassing at all, and they would still remain a quite useful construct. Polymorphism (when needed) is often better achieved using protocols rather than subclassing, anyway.

(Note that we clearly do not live in that universe. I don’t think it would be a good idea to remove subclassing from Swift.)

I suppose you could make the case you’d want to inherit from a parent class but not allow other classes to inherit from you. This just seems like more of a mess though. I could have vendors shipping me view controllers that I can’t inherit from, complicating my own designs.

We already have final, so this is already the case today. For what it’s worth, I’ve already made many of my own view controllers final; but I’m not vending them out to people.

(The other big reason I still use a lot of classes in Swift is Cocoa compatibility, but I’m assuming Obj-C compatible Swift objects won’t support final or final-by-default anyway.)

That is a good question; @objc and final are not mutually exclusive today. I think cross-module subclassibility should work the same way.

···

On 2016-07-12, at 01:54, Colin Cornaby <colin.cornaby@mac.com> wrote:

Someone correct me if I’m missing something big here though.

--
Karoly
@lorentey

Safety is valued, but Swift cares (and should care!) about pragmatism as well… the most obvious example that comes to my mind are arrays, which have no safeguards that stop you from accessing elements that aren't there.

I'm not sure I understand this point. First, a guaranteed runtime trap on an out-of-bounds index is a *massive* safety improvement over what we had in C/C++/Objective-C.

… but still, it wouldn't it be much safer to return an optional?

Second, safety is explicitly goal #1 for Swift; it's even more important than performance and expressiveness (although fortunately these aren't always at odds).

Reality is more complicated, and it's hard to measure safety — sometimes it is not even possible to compare two different solutions in terms of general safety.
In the array-example, safety doesn't trump over performance & convenience, and I wouldn't want to use a language without such exceptions (which would mean that even a infinitesimally increase in safety would justify a huge loss in performance, expressiveness and convenience).

I've made some notes of the arguments (pro and con) that I've seen in this thread and elsewhere; see them below. I think we have seen ample evidence in support for most of them, from both sides. As usual, our differences are difficult/impossible to reconcile. People may have become a bit theatrical at times, but the discussions I've read have been far from dogmatic.

Well, there are some of us who could reconcile their differences — simply be choosing neither sealed nor extendable to be the default… imho this would be the best match for the situation, but "force override" doesn't seem to gain much interest.

I would have chosen a different order, but overall, imho your list looks like you honestly tried to avoid bias — and maybe even achieved this high goal.
Only two tiny additions:

- The Weirdo Argument: This list represents a very curious subset of Swift developers, who are very different to the vast majority of people using Swift. Normal people just want to get their work done, and they would be infuriated if such a change would be implemented. This list is a quite exotic bubble, and it can be fatal to assume that it is representative for the mass of developers that have to deal with the consequences of its discussions. Programming languages are best evolved in a strictly democratic manner.

As this point closely matches my wording, I have to correct it.
I never said Swift should be evolved in a democratic manner at all, and I absolutely don't think it should.
What I said is that the members of this list are not representative for the majority of developers using Swift, because only a small and somewhat uniform subset is inclined to take part in the discussions.
I guess it's save to assume that there some common characteristics which are much less dominant outside this circle:
- Interest in language design
- A certain degree of theoretical background
- A stable opinion on how software should be written
- Spending time for theoretical discussions instead of developing actual software ;-)
So, this list is definitely some sort of ivory tower, and there is a constant danger for Swift to become a language that pleases a small elite, but ignores the needs of the majority — and I think that Swift specifically aims for the majority.
I myself enjoy over-engineering my code and discard working solutions in favor for a more elegant design, but I know that this is not representative, and I accept reality… and that is that there are many projects built in a fashion that is completely different from how it's said in the books.
"Design for inheritance" is a good idea (not only in theory), but many codebases have hardly any design at all, simply because its author doesn't have enough knowledge to do better.
There is a good motivation to punish bad design, but bad design is quite indifferent about torture, so the only perception of the uninformed developer is that he himself is punished by the language for reasons beyond his interest:
The compiler cannot educate its users, because all they perceive is an annoying call to switch to another file and perform a trivial change.

That's already more text than I wanted to write, but there's still an argument left that's particular important to me:
Choice of defaults is a statement, and therefor relevant for the character of the language — and what this proposal says about Swift is "keep control to yourself, restrict other developers and focus on concealment".
For me, those aren't traits worth striving for.

Tino

There will be no need for an *extra* keyword if we use a keyword that replaces `public`, like the proposed keywords or my preferred `open`. It would merely be a *different* keyword.

···

On Jul 11, 2016, at 3:51 PM, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:

- Annoyance: People will need to remember to manually add an extra keyword to their public classes whenever they want to allow subclassing. People who want to always allow cross-module subclassing will always have to add the keyword. The extra work to do this would be annoying and pointless, and the proliferation of such additional keywords would make Swift needlessly wordy.

--
Brent Royal-Gordon
Architechies

Tino,

This argument comes up often, but I side with some of the smartest people in the industry who say the safest thing to do is actually crash, just like Swift does in this case.

If you are accessing an element in an array that doesn’t exist, you didn’t reason about the contents of said array correctly. Therefore, whatever you were planning to do, something in your logic is seriously wrong. It’s safer to say “Stop!” at this stage, rather than let you proceed further into the bug infested mess you’re about to walk into. Other side effects might occur, both in your model code, and you may also be leaving your UI in a completely random and inconsistent state. Flow on effects could even include deleting incorrect files from disk if you’re using the array to store file URLs, and because you don’t know your indexes correctly, you’re risking deleting the wrong file.

Experience has shown that it is safer to bring your software to a very abrupt stop rather than hope you can handle this situation. After all… you clearly don’t know about your current state, so how can you know how to correctly recover from it?

From what I understand, this was the Core Team’s reasoning for not returning optionals from array indexes, and I support that decision.

As an aside, I actually do have my own personal “ifExists:” subscript that runs the correct checks and returns an optional. I have specific use cases for it where it does make sense. But I can see the danger of including something like that in the Standard Library. It’s a rare case, and would open up the can of worms when everyone starts using it, instead of actually correctly reasoning about their code.

- Rod

···

On 12 Jul 2016, at 4:53 PM, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

Safety is valued, but Swift cares (and should care!) about pragmatism as well… the most obvious example that comes to my mind are arrays, which have no safeguards that stop you from accessing elements that aren't there.

I'm not sure I understand this point. First, a guaranteed runtime trap on an out-of-bounds index is a *massive* safety improvement over what we had in C/C++/Objective-C.

… but still, it wouldn't it be much safer to return an optional?\

After all… you clearly don’t know about your current state, so how can you know how to correctly recover from it?

This a bit of a stretch, it is often the case but not a necessary conclusion. Both C++ and Java have a model where it is not uncommon to recover from exceptions instead of crashing. You might know enough about your context to be able to go back to a safe point in time/app lifecycle and resume from there.

Also, the more scenarios we want to code in pure Swift in the more cases in which other languages offer features we do not want in Swift now we are going to encounter. The pragmatic voice in engineers will start asking "why do I need to jump through hoops for purity's sake"?

···

Sent from my iPhone

On 12 Jul 2016, at 09:10, Rod Brown via swift-evolution <swift-evolution@swift.org> wrote:

After all… you clearly don’t know about your current state, so how can you know how to correctly recover from it?

This argument comes up often, but I side with some of the smartest people in the industry who say the safest thing to do is actually crash, just like Swift does in this case.

I don't like supporting positions with claims like "the smartest people say", and I personally renounce from doing so — even when I can cite a tangible name… and I think that you are walking on thin ice here in an attempt to prove something wrong just because it's coming from the other party in an discussion:
If being forced to check for existence of data is really less safe than crashing, a major element of Swift is nonsense.
Imho it's smarter to admit that this is a case where Swift does not want to pay the price for a little increase in safety — after all, this is only a sideshow with little effect on the actual topic.

After all… you clearly don’t know about your current state, so how can you know how to correctly recover from it?

This a bit of a stretch, it is often the case but not a necessary conclusion. Both C++ and Java have a model where it is not uncommon to recover from exceptions instead of crashing. You might know enough about your context to be able to go back to a safe point in time/app lifecycle and resume from there.

I was not meaning to state that as an absolute, but as a generalisation. As I mentioned later, I do have cases where it's appropriate to use an ifExists subscript.

That said, Swift's safety isn't generally built around "safe from crashes". After all, unwrapping an optional in Swift is a fatal error, and sending a message to nil in Obj-C is a no-op! So Obj-C is safer! ... um... no. It's safer in that it forces you to reason about your code rather than letting nil be no-op. That sounds remarkably similar to making someone reason about accessing an array and crashing, rather than just letting it be a no-op.

You'll also notice that Swift doesn't have the exception handling that C++ and Java do, as you referenced. Again, this appears to be in the same train of thought: if you've met an exception, you didn't reason about your code, and the safest thing to do is quit. Swift does have error handling, on the other hand, understanding that errors can occur that are not related to inconsistent reasoning about your code.

Are there ways you could recover? Sure, in some circumstances. I could say the same thing about incorrectly unwrapping nil, or any other error of logic. I think Swift comes down hard on the side of "if there are errors in logic, just crash now, it's the best overall choice."

Do I agree with it? Personally, I do, as I think it's a pragmatic decision overall. I can definitely see why others may disagree, and please feel free to add ifExists subscript in your projects like I have.

Also, the more scenarios we want to code in pure Swift in the more cases in which other languages offer features we do not want in Swift now we are going to encounter. The pragmatic voice in engineers will start asking "why do I need to jump through hoops for purity's sake"?

I don't think this is a purity issue. It seems pragmatic to crash as it's almost always a programmer error that would cause this situation. A simple check on count would fix this in any other cases. Encouraging people to Handle programmer errors rather than simply Fix them seems rather odd to me.

···

Sent from my iPhone
On 12 Jul. 2016, at 6:19 pm, Goffredo Marocchi <panajev@gmail.com> wrote:

Sent from my iPhone

On 12 Jul 2016, at 09:10, Rod Brown via swift-evolution <swift-evolution@swift.org> wrote:

After all… you clearly don’t know about your current state, so how can you know how to correctly recover from it?