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

I wanted to share a concrete use case that Daniel Dunbar relayed to me. He was working on a closed class hierarchy like the ones discussed here, where all of the subclasses are within a single module, but they are all public. The class also has a required initializer for dynamic construction, so that they could write something like this:
internal struct ModelContext { /*…*/ }
public class ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
  // …
}
public class SimpleModel: ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
}
public class MoreComplicatedModel: ModelBase { /*…*/ }
// (within some other type)
public func instantiateModelObject<Model: ModelBase>(_ type: Model) -> Model {
  return type.init(context: self.context)
}
That is, a public entry point calls a required initializer with an internal argument type. This is the only way to instantiate Model objects, and the internal context type doesn’t leak out into the public API.
Of course, Swift doesn’t allow this. If someone outside of the module subclasses ModelBase, there’s no way for them to provide the dynamically-dispatched 'init(context:)’, because they don’t have access to the internal ModelContext. The author of the library has to make the required initializers public, and either set the ModelContext separately or make it public as well. Even though no one outside the module should be using these APIs.

Can you remind us why does Swift need required initializers to have the same access as the containing class?

Because without required initializers you can't do something like:

let cl: MyClass.Type = ...
let instance = cl.init(value: 0)

where MyClass has a required init(value: Int).

The issue here is that in order to be able to do this, subclasses that implement another initializer must implement this one as well - so it must be visible outside of the module if it is public.

···

On Jul 10, 2016, at 1:37 AM, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:
On 2016-07-09 04:39:01 +0000, Jordan Rose via swift-evolution said:

Having only package-internal constructors in the public base class is the usual pattern for exporting a sealed class hierarchy in some other languages, like Java.

--
Károly
@lorentey

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I’m afraid it isn’t my codebase, so I can’t speak to this directly, but I don’t see anything wrong with this pattern. Dynamic initialization is useful, and internal, component-specific context is hardly uncommon; if the library used either of these alone we would likely have no problem with it. Two features not composing well does sometimes indicate a flaw in the design.

Jordan

···

On Jul 8, 2016, at 23:47, L. Mihalkovic <laurent.mihalkovic@gmail.com> wrote:

Regards
(From mobile)

On Jul 9, 2016, at 6:39 AM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md ]

John has done a tremendous job supporting this proposal; the position he’s articulated very closely matches mine. Thank you to both John and Javier.

I wanted to share a concrete use case that Daniel Dunbar relayed to me. He was working on a closed class hierarchy like the ones discussed here, where all of the subclasses are within a single module, but they are all public. The class also has a required initializer for dynamic construction, so that they could write something like this:

internal struct ModelContext { /*…*/ }

public class ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
  // …
}
public class SimpleModel: ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
}
public class MoreComplicatedModel: ModelBase { /*…*/ }

// (within some other type)
public func instantiateModelObject<Model: ModelBase>(_ type: Model) -> Model {
  return type.init(context: self.context)
}

That is, a public entry point calls a required initializer with an internal argument type. This is the only way to instantiate Model objects, and the internal context type doesn’t leak out into the public API.

Then could it be that in the end it is the entire scaffolding that is poorly structured and in need of fixing, rather than altering the language to make the scaffolding work?

Sent from my iPhone

Of course, Swift doesn’t allow this. If someone outside of the module subclasses ModelBase, there’s no way for them to provide the dynamically-dispatched 'init(context:)’, because they don’t have access to the internal ModelContext.

Shouldn't Swift allow this? Wouldn't it be better if we found a different way to handle this than a brute force "you shall only subclass if I think you should"? Is that really an impossible cause that is worth us going completely the opposite direction of most programming languages?

There is no way to implement the required initializer from outside the module, because it uses an internal type, so what we’re looking for is that any subclasses from outside the module will never have the required initializer invoked on them. I suppose it would still be safe to allow a subclass from outside the module that did not provide any of its own initializers, but that seems like an even more complicated rule.

(It’s not sufficient to say that the dynamic initializers would just trap at run-time, because it’s possible that the base class has no public initializers.)

Can you tell me why the onus should not be on you, on library authors, to use final or an equivalent keyword to indicate no subclassing is allowed and thus make this intentional?

I am really not sold on why classes should not be subclassable by default. Not all classes suffer of the problem you mention and for those cases you should be able to express your intention explicitly. I am quite against this being a compiler default.

I admit that this use case says nothing about whether “sealed” should be the default or just available.

I think that security by ignorance, which is what automagically enforced rules tend to produce over time, does have some side effects.

This isn’t really a security issue; it’s a compiler-aided correctness issue. I’ll go more into that in my other email.

Jordan

···

On Jul 9, 2016, at 01:44, Goffredo Marocchi <panajev@gmail.com> wrote:

On 9 Jul 2016, at 05:39, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

Sure. A required initializer is one that will be invoked on an unknown subclass of the type, as in the instantiateModelObject method above. That means that it must be present on all subclasses, because it’s generally impossible to prove that a particular subclass will not be passed to instantiateModelObject.

Swift currently doesn’t allow this at all, but we could imagine making it a run-time error instead of a compile-time error, i.e. if instantiateModelObject is called on a class that doesn’t provide its own implementation of init(context:), the program would trap. That’d be giving up some compiler-provided safety for run-time flexibility, which can certainly be desirable.

However, this is just the tip of the iceberg. In the implementation of the subclass, there has to be a call to one of the superclass's initializers. If all of the superclass’s initializers are non-public, then there’s no way to write your own initializer. (This is actually true in Swift today.) This isn’t quite the same as sealed-by-default because the subclass could just inherit the superclass’s initializers, but it’s very close: the only way to instantiate such a class would be through a required initializer, which you couldn’t customize.

In that sense, Swift already has something very similar to sealed-by-default, which just doesn’t work very well in the particular use case where you also need to use dynamic initialization.

Jordan

···

On Jul 9, 2016, at 16:37, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:

On 2016-07-09 04:39:01 +0000, Jordan Rose via swift-evolution said:

I wanted to share a concrete use case that Daniel Dunbar relayed to me. He was working on a closed class hierarchy like the ones discussed here, where all of the subclasses are within a single module, but they are all public. The class also has a required initializer for dynamic construction, so that they could write something like this:
internal struct ModelContext { /*…*/ }
public class ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
  // …
}
public class SimpleModel: ModelBase {
  internal required init(context: ModelContext) { /*…*/ }
}
public class MoreComplicatedModel: ModelBase { /*…*/ }
// (within some other type)
public func instantiateModelObject<Model: ModelBase>(_ type: Model) -> Model {
  return type.init(context: self.context)
}
That is, a public entry point calls a required initializer with an internal argument type. This is the only way to instantiate Model objects, and the internal context type doesn’t leak out into the public API.
Of course, Swift doesn’t allow this. If someone outside of the module subclasses ModelBase, there’s no way for them to provide the dynamically-dispatched 'init(context:)’, because they don’t have access to the internal ModelContext. The author of the library has to make the required initializers public, and either set the ModelContext separately or make it public as well. Even though no one outside the module should be using these APIs.

Can you remind us why does Swift need required initializers to have the same access as the containing class?

Having only package-internal constructors in the public base class is the usual pattern for exporting a sealed class hierarchy in some other languages, like Java.

DaveA and I actually talked about this, and IIRC we decided it was completely safe semantically, and only left a few optimization opportunities on the table (things like knowing what a deinitializer might or might not do). However, we felt it was a more complicated model that still ended up with the “de facto sealed” case of all initializers being non-public, and so it wasn’t worth stopping at that point in the design space.

(It was a while ago, so I might have forgotten something else relevant.)

Jordan

···

On Jul 11, 2016, at 07:21, Jean-Daniel Dupas <mailing@xenonium.com> wrote:

Just a though, but why sealed classes have to be completely unsubclassable ?

Wouldn't it be possible to allow the user to subclass sealed class, but deny overriding of any public member.

I see a use case where a user want to extends an existing model by adding new properties and new methods to an object but can’t use composition because doing that will prevent to pass that object to the framework that expect the base object.

That would let user override existing class to extends them, but should not cause any side effect in the way the class should behave, and so would not affects preconditions and postconditions, and should not prevent optimization in whole module compilation, as the methods of the base class are considered final outside of the module.

Of course, it will introduce some fragility in the library, as adding new methods may conflict with user subclass methods, but no more than what would append if the user write extension to add new methods to the model.

P.S. There’s also an argument to be made for public-but-not-conformable protocols, i.e. protocols that can be used in generics and as values outside of a module, but cannot be conformed to. This is important for many of the same reasons as it is for classes, and we’ve gotten a few requests for it. (While you can get a similar effect using an enum, that’s a little less natural for code reuse via protocol extensions.)

Would public-but-not-conformable protocols by default be the next step, then, in Swift's evolution?

I personally think it’s a reasonable place to go next, which is why I brought it up. However, I don’t think it’s critical enough to get into Swift 3 when we’re already so busy, and when there are multiple non-source-breaking ways to get a similar effect later: adding a “sealed” annotation (so, giving up on “by default” for protocols) and allowing requirements to have more narrow access than the protocol (thus making it impossible to conform).

Jordan

Jean, given this proposal it will be possible if the developer of the
library intends so. You'll have to have unsealed classes to be able to
subclass them and unsealed methods so you can override. It is possible
to just allow subclassing without allowing overriding, just like
final.

As for conflicts I don't think so. If you declare a new method with
the same name as an existing one without overriding, it will become a
new method and the base class won't even know that new method exists.
C# allows this but uses the keyword new (instead of override) to
clarify a new method is being introduced instead of the existing one
but as far as I see there is no such need in Swift. I'm also not sure
we can override a method inside an extension but if so, this provides
a new point of extension inside a class that is not subclassable.

L

···

On 11 July 2016 at 11:21, Jean-Daniel Dupas via swift-evolution <swift-evolution@swift.org> wrote:

Just a though, but why sealed classes have to be completely unsubclassable ?

Wouldn't it be possible to allow the user to subclass sealed class, but deny
overriding of any public member.

I see a use case where a user want to extends an existing model by adding
new properties and new methods to an object but can’t use composition
because doing that will prevent to pass that object to the framework that
expect the base object.

That would let user override existing class to extends them, but should not
cause any side effect in the way the class should behave, and so would not
affects preconditions and postconditions, and should not prevent
optimization in whole module compilation, as the methods of the base class
are considered final outside of the module.

Of course, it will introduce some fragility in the library, as adding new
methods may conflict with user subclass methods, but no more than what would
append if the user write extension to add new methods to the model.

Le 11 juil. 2016 à 05:38, Jordan Rose via swift-evolution > <swift-evolution@swift.org> a écrit :

[Proposal:
https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md
]

(This is my second response to this proposal. The previous message shared a
use case where public-but-non-subclassable made things work out much better
with required initializers. This one has a bit more ideology in it.)

As many people have said already, this proposal is quite beneficial to
library designers attempting to reason about their code, not just now but in
the future as well. The model laid out in the Library Evolution document
(often referred to as “resilience”) supports Swift libraries that want to
preserve a stable binary and source interface.

In the Swift 2 model (and what’s currently described in that document), a
public class must be final or non-final at the time it is published. It’s
clearly not safe to add ‘final' in a later version of the library, because a
client might already have a subclass; it’s also not safe to remove ‘final’
because existing clients may have been compiled assuming there are no
subclasses.

(Of course, we can remove this optimization, and make ‘final’ a semantic
contract only. I’m deliberately avoiding most discussion of performance, but
in this parenthetical I’ll note that Swift makes it possible to write code
that is slower than Objective-C. This is considered acceptable because the
compiler can often optimize it for a particular call site. For those who
want more information about the current implementation of some of Swift’s
features, I suggest watching the “Understanding Swift Performance” talk from
this year’s WWDC.)

With this proposal, a public class can be non-publicly-subclassable or
publicly-subclassable. Once a class is publicly-subclassable (“open”), you
can’t go back, of course. But a class that’s not initially open could become
open in a future release of the library. All existing clients would already
be equipped to deal with this, because there might be subclasses inside the
library. On the other hand, the class can also be marked ‘final’, if the
library author later realizes there will never be any subclasses and that
both client authors and the compiler should know this.

One point that’s not covered in this proposal is whether making a class
‘open’ applies retroactively, i.e. if MagicLib 1.2 is the first version that
makes the Magician class ‘open’, can clients deploy back to MagicLib 1.0 and
expect their subclasses to work? My inclination is to say no; if it’s
possible for a non-open method to be overridden in the future, a library
author has to write their library as if it will be overridden now, and
there’s no point in making it non-open in the first place. That would make
‘open’ a “versioned attribute” in the terminology of Library Evolution,
whatever the syntax ends up being.

---

Okay, so why is this important?

It all comes down to reasoning about your program’s behavior. When you use a
class, you’re relying on the documented behavior of that class. More
concretely, the methods on the class have preconditions
(“performSegue(withIdentifier:sender:) should not be called on a view
controller that didn’t come from a storyboard”) and postconditions (“after
calling loadViewIfNeeded(), the view controller’s view will be loaded”).
When you call a method, you’re responsible for satisfying its preconditions
so it can deliver on the postconditions.

I used UIViewController as an example, but it applies just as much to your
own methods. When you call a method in your own module—maybe written by you,
maybe by a coworker, maybe by an open source contributor—you’re expecting
some particular behavior and output given the inputs and the current state
of the program. That is, you just need to satisfy its preconditions so it
can deliver on the postconditions. If it’s a method in your module, though,
you might not have taken the trouble to formalize the preconditions and
postconditions, since you can just go look at the implementation. Even if
your expectations are violated, you’ll probably notice, because the conflict
of understanding is within your own module.

Public overriding changes all this. While an overridable method may have
particular preconditions and postconditions, it’s possible that the
overrider will get that wrong, which means the library author can no longer
reason about the behavior of their program. If they do a poor job
documenting the preconditions and postconditions, the client and the library
will almost certainly disagree about the expected behavior of a particular
method, and the program won’t work correctly.

"Doesn’t a library author have to figure out the preconditions and
postconditions for a method anyway when making it public?" Well, not to the
same extent. It’s perfectly acceptable for a library author to document
stronger preconditions and weaker postconditions than are strictly
necessary. (Maybe 'performSegue(withIdentifier:sender:)’ has a mode that can
work without storyboards, but UIKit isn’t promising that it will work.) When
a library author lets people override their method, though, they're
promising that the method will never be called with a weaker precondition
than documented, and that nothing within their library will expect a
stronger postcondition than documented.

(By the way, the way to look at overriding a method is the inverse of
calling a method: you need to deliver on the postconditions, and you can
assume the caller has satisfied the preconditions. If your understanding of
those preconditions and postconditions is wrong, your program won’t work
correctly, just like when you’re calling a method.)

This all goes double when a library author wants to release a new version of
their library with different behavior. In order to make sure existing
callers don’t break, they have to make sure all of the library’s documented
preconditions are no stronger and postconditions are no weaker for public
API. In order to make sure existing subclassers don’t break, they have to
make sure all of the library’s documented preconditions are no weaker and
postconditions are no stronger for overridable API.

(For a very concrete example of this, say you’re calling a method with the
type '(Int?) -> Int’, and you’re passing nil. The new version of the library
can’t decide to make the parameter non-optional or the return value
optional, because that would break your code. Similarly, if you’re
overriding a method with the type ‘(Int) -> Int?’, and returning nil, the
new version of the library can’t decide to make the parameter optional or
the return value non-optional, because that would break your code.)

So, "non-publicly-subclassable" is a way to ease the burden on a library
author. They should be thinking about preconditions and postconditions in
their program anyway, but not having to worry about all the things a client
might do for a method that shouldn’t be overridden means they can actually
reason about the behavior—and thus the correctness—of their own program,
both now and for future releases.

---

I agree with several people on this thread that
non-publicly-subclassable-by-default is the same idea as
internal-by-default: it means that you have to explicitly decide to support
a capability before clients can start relying on it, and you are very
unlikely to do so by accident. The default is “safe” in that a library
author can change their mind without breaking existing clients.

I agree with John that even today, the entry points that happen to be public
in the types that happen to be public classes are unlikely to be good entry
points for fixing bugs in someone else's library. Disallowing overriding
these particular entry points when a client already can't override internal
methods, methods on structs, methods that use internal types, or top-level
functions doesn’t really seem like a loss to me.

Library design is important. Controlling the public interface of a library
allows for better reasoning about the behavior of code, better security
(i.e. better protection of user data), and better maintainability. And
whether something can be overridden is part of that interface.

Thanks again to Javier and John for putting this proposal together.
Jordan

P.S. There’s also an argument to be made for public-but-not-conformable
protocols, i.e. protocols that can be used in generics and as values outside
of a module, but cannot be conformed to. This is important for many of the
same reasons as it is for classes, and we’ve gotten a few requests for it.
(While you can get a similar effect using an enum, that’s a little less
natural for code reuse via protocol extensions.)

P.P.S. For those who will argue against “better security”, you’re correct:
this doesn’t prevent an attack, and I don’t have much expertise in this
area. However, I have talked to developers distributing binary frameworks
(despite our warnings that it isn’t supported) who have asked us for various
features to keep it from being easy.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

This is something you’d need if the client were itself a library that couldn’t break backwards-compatibility by renaming its conflicting method. We could either have a “new” or “nonoverriding” annotation or a way to rename methods while preserving their binary interface, but

You cannot safely override a method inside an extension unless you also control the original method. See my analysis in this draft of a future proposal <https://github.com/jrose-apple/swift-evolution/blob/dc5b0cd7d646c161ede08800cdb4ec90286c6a44/proposals/nnnn-overridable-members-in-extensions.md&gt;\.

Jordan

···

On Jul 11, 2016, at 08:13, Leonardo Pessoa <me@lmpessoa.com> wrote:

Jean, given this proposal it will be possible if the developer of the
library intends so. You'll have to have unsealed classes to be able to
subclass them and unsealed methods so you can override. It is possible
to just allow subclassing without allowing overriding, just like
final.

As for conflicts I don't think so. If you declare a new method with
the same name as an existing one without overriding, it will become a
new method and the base class won't even know that new method exists.
C# allows this but uses the keyword new (instead of override) to
clarify a new method is being introduced instead of the existing one
but as far as I see there is no such need in Swift. I'm also not sure
we can override a method inside an extension but if so, this provides
a new point of extension inside a class that is not subclassable.

I haven't read the whole Library Evolution document, but one important part is written right at the top:

This model is largely not of interest to libraries that are bundled with their clients (distribution via source, static library, or embedded/sandboxed dynamic library, as used by the Swift Package Manager <https://swift.org/package-manager/&gt;\)

So there are compelling arguments to "seal" the Apple-libraries, and it's reasonable to enforce sealing on them.
But if sealed is the right default for those libraries, it is not automatically the right default for all other libraries out there, because those are developed in a completely different manner.
So, instead making sealed the default for Swift, I believe it is much more sound to just make it the default for the standard frameworks:
This doesn't break compatibility, it's imho more convenient for the majority, and I guess there is enough manpower to manage the annotations for Cocoa and other frameworks (which is tedious labor for single developers, but no issue for a large company).

The binary compatibility concerns are less important, but the ability of a library author to reason about behavior is still critical. Here’s a scenario I really don’t want to see happen:

1. Client X adds a dependency on library A, via the package manager. They override a public method in library A to get the behavior they want.
2. Library A ships an update which fixes many bugs and security issues, and happens to eliminate the internal calls to the public method A was using. That is, overriding Athat method no longer has any effect within library A; only clients see the difference.
3. The developer for client X goes to their manager and asks for time to fix the issue. The manager tells them not to update right now, but maybe after this next release.
4. Client X never gets a new version of Library A ever again.

This hypothetical scenario happened because the client and the library had differing notions about what you could do with this particular public method. From the library point of view, this shouldn’t be a source-breaking change because they never expected it to do anything; from the client point of view, the program does a perfectly normal thing of overriding a public API and the library has capriciously broken it.

I guess another way of saying this is that library authors avoid these miscommunications by "under-promising”, and if that “under-promising” is something that’s actually in the language instead of maybe/maybe-not being documented, then both sides are much more likely to be on the same page.

While an overridable method may have particular preconditions and postconditions, it’s possible that the overrider will get that wrong, which means the library author can no longer reason about the behavior of their program.

Once again a situation where we have to differentiate wether we encourage open source or not:
In OS, users of a library become the allies of its author — they can put stress on his model, they can find its flaws and they can show him how to improve.
The ability to take a piece of code and start playing with it is a fantastic trait, and this is actively discouraged by imposing limits not because they make sense, but only because the original author didn't take the time to reason about the status.

This is completely true, but I don’t see it as having much to do with subclassing. If a library is open source, most of your “playing with it” will be modifying the original code, not using a subclass. This is especially true given how many Swift libraries are made up of just structs, enums, and protocols. Additionally, “build for testing” will also allow subclassing sealed classes (though not final classes, since the ‘final’ is part of the semantics rather than just controlling access).

For software that grows "organically", documentation is more useful than simple rules...
I like the concept of version blocks, and it could work in the other direction as well: We could have a "experimental"-modifier that would give the library author a way to offer hints for its clients, but leaves the final decision up to them. This would be much more granular than a plain "final" which not only protects those who want to stay on the safe side, but also repels the bold developers who'd willingly help improving the code in question.

If you were writing a library, what would make you decide between “experimental” and not?

Jordan

···

On Jul 11, 2016, at 04:34, Tino Heth <2th@gmx.de> wrote:

Am 11.07.2016 um 05:38 schrieb Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

OK,

Thanks for the explanation and the long argumentation about why sealed by default make sens. You made me change my stance about the proposal. I just hope the seal keyword will be properly explained and will not make subclassing a second class citizen by discouraging developer to support it.

···

Le 11 juil. 2016 à 17:43, Jordan Rose <jordan_rose@apple.com> a écrit :

On Jul 11, 2016, at 07:21, Jean-Daniel Dupas <mailing@xenonium.com> wrote:

Just a though, but why sealed classes have to be completely unsubclassable ?

Wouldn't it be possible to allow the user to subclass sealed class, but deny overriding of any public member.

I see a use case where a user want to extends an existing model by adding new properties and new methods to an object but can’t use composition because doing that will prevent to pass that object to the framework that expect the base object.

That would let user override existing class to extends them, but should not cause any side effect in the way the class should behave, and so would not affects preconditions and postconditions, and should not prevent optimization in whole module compilation, as the methods of the base class are considered final outside of the module.

Of course, it will introduce some fragility in the library, as adding new methods may conflict with user subclass methods, but no more than what would append if the user write extension to add new methods to the model.

DaveA and I actually talked about this, and IIRC we decided it was completely safe semantically, and only left a few optimization opportunities on the table (things like knowing what a deinitializer might or might not do). However, we felt it was a more complicated model that still ended up with the “de facto sealed” case of all initializers being non-public, and so it wasn’t worth stopping at that point in the design space.

(It was a while ago, so I might have forgotten something else relevant.)

Jordan

Done.

John.

···

On Jul 5, 2016, at 10:56 PM, Chris Lattner <clattner@apple.com> wrote:

On Jul 5, 2016, at 6:53 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

I think Kevin Lundberg is right to worry about testability, but I don't think that has to prevent this change. Instead, we should permit `@testable` imports to subclass/override things that are not publicly subclassable/overridable, and thus a module built with "Enable Testability" on can't actually assume there are no subclasses/overrides of `internal` classes/members even if it doesn't see any. This will block optimizations in debug builds, but not in release builds. The proposal should be edited to explain this `@testable` behavior.

IIUC the basic design of @testable is to treat the tests for the testable thing as existing within its module, so I think this just falls out. I agree that it should be spelled out in the proposal, though.

That makes sense to me. Please explicitly add that to the proposal, thank you!

I hadn't considered @testable, and it may be one way to mitigate the
trouble this could cause in tests, so thank you both for bringing it up
as the proposal should definitely account for it. I'm curious though how
this would solve the case of trying to subclass a module's class in a
test where you don't have the source? If you don't have control over the
source, you can't rebuild it to enable testability, and it might even be
desirable for someone to refuse to distribute a binary with testability
enabled if doing so might reveal proprietary information or lead to a
possible security problem with their library.

Our testing design is only intended to allow first-party tests to break the
normal access restrictions. It seems to me that other tests should be
treating their external dependencies as black boxes and use their normal API.

John.

···

On Jul 5, 2016, at 9:11 PM, Kevin Lundberg <kevin@klundberg.com> wrote:

On 7/5/2016 9:53 PM, John McCall via swift-evolution wrote:

On Jul 5, 2016, at 6:45 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

  * What is your evaluation of the proposal?

I think it's ultimately a good idea. Being noncommittal about subclassability/overridability—and thus forbidding it in public scope, but not making any promises about what the module does internally—is the alternative that preserves the most freedom for the module, and therefore the most appropriate default.

However, I don't like the `subclassable` and `overridable` keywords. They read like opposites of `final` with no implications for access level; I could easily imagine somebody marking an internal class `subclassable` assuming that it merely means it can be subclassed internally, and being very surprised (or even not noticing!) that the class has been made public. They're also long and cumbersome; that might be seen as a positive, but I think it will increase the inevitable backlash against this change.

I prefer the keyword `open`, which sounds like it could be a statement about the item's accessibility—and even sounds like it ought to be "more public than public"—and is short enough that it ought to be difficult to grumble about the change. It also means that both classes and members use the same keyword, and gives us a keyword that we can later use to "open" other things in the language, such as allowing you to extend enums with new cases.

Agreed; I also prefer "open" to having two different long keywords that don't (at least to my ear) imply "public".

I think Kevin Lundberg is right to worry about testability, but I don't think that has to prevent this change. Instead, we should permit `@testable` imports to subclass/override things that are not publicly subclassable/overridable, and thus a module built with "Enable Testability" on can't actually assume there are no subclasses/overrides of `internal` classes/members even if it doesn't see any. This will block optimizations in debug builds, but not in release builds. The proposal should be edited to explain this `@testable` behavior.

IIUC the basic design of @testable is to treat the tests for the testable thing as existing within its module, so I think this just falls out. I agree that it should be spelled out in the proposal, though.

John.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Scott, I think your writing got a bit confuse but, if I got your
intention right, since you are the owner of the class, you may choose
to subclass it or not internally, no questions asked.

No.

This is how the language exists today:

1a. Default access control is `internal`, and may be modified by adding the `public`, `fileprivate`, or `private` keyword, as appropriate.

1b. All classes may be subclassed, and all methods, properties, and subscripts overridden. This can be prevented by adding the `final` keyword.

The two concepts are very separate, and I think this is a good thing.

What this proposal suggests, whether or not `final` is kept or removed, is that the two concepts become conflated:

2a. Default access control is `internal`, and may be modified by adding the `public`, `fileprivate`, or `private` keyword, as appropriate.

2b. Non-`public` classes may be subclassed, and non-`public` methods, properties, and subscripts overridden. This can be prevented by adding the `final` keyword.

2c. `public` classes may not be subclassed. To allow this replace the `public` keyword with `subclassable`

2d. `public` methods, properties, and subscripts may not be overridden. To allow this replace the `public` keyword with `overiddable`.

Removing the `final` keyword means a change to 2b above. The first option is simply to throw it away, in which case you end up with:

3a. Default access control is `internal`, and may be modified by adding the `public`, `fileprivate`, or `private` keyword, as appropriate.

3b. Non-`public` classes may be subclassed, and non-`public` methods, properties, and subscripts overridden. This can be never be prevented.

3c. `public` classes may not be subclassed. To allow this replace the `public` keyword with `subclassable`

3d. `public` methods, properties, and subscripts may not be overridden. To allow this replace the `public` keyword with `overiddable`.

The second option is to throw it away, and adjust the default behavior so it truly is `final` as removing the keyword would imply. Then you end up with:

4a. Default access control is `internal`, and may be modified by adding the `public`, `fileprivate`, or `private` keyword, as appropriate.

4b. Non-`public` classes may not be subclassed, and non-`public` methods, properties, and subscripts may not be overridden. This can never be allowed.

4c. `public` classes may not be subclassed. To allow this replace the `public` keyword with `subclassable`

4d. `public` methods, properties, and subscripts may not be overridden. To allow this replace the `public` keyword with `overiddable`.

To me all of these options take a clean, easy-to-understand, language design (1) and turn it into various states of mess. Removing the `final` keyword not only makes it a mess, but removes functionality—the ability to have a mix of final and non-final classes, methods, properties, and subscripts, accessible only within your own module.

Scott

···

On Jul 6, 2016, at 1:16 PM, Leonardo Pessoa <me@lmpessoa.com> wrote:

So I did get what you meant right. Now tell me: if a class or method
is internal to your module (and you know internal means only you,
throught your source code, inside your app or library can extend it),
do you really need to mark anything as final for any reason? Final on
any non-publics is a restriction you put on yourself, you will always
have the power to lift that at any time (as long as you still own the
code, of course) so you are always in control of your subclasses and
overrides. All the time. It's up to you to subclass/override or not.
This is different from what you make public: you either have no
control or you have to fine grain control making everything final. You
regain that control over your publics with this proposal.

You can also try and simplify your outlines reducing X.c and X.d to a
single entry as it is the same rule applied to two different elements
of the language. Using one single keyword (such as in 'open') would
make it clearer and that is why I prefer to have only one keyword.

So, AFAICS, number 3 is how it should be with this proposal. As for
keeping the final keyword (sticking with number 2), I think it is up
to the community. I myself don't see any reason/need for it should
this proposal pass.

L

···

On 6 July 2016 at 17:32, Scott James Remnant <scott@netsplit.com> wrote:

On Jul 6, 2016, at 1:16 PM, Leonardo Pessoa <me@lmpessoa.com> wrote:

Scott, I think your writing got a bit confuse but, if I got your
intention right, since you are the owner of the class, you may choose
to subclass it or not internally, no questions asked.

No.

This is how the language exists today:

1a. Default access control is `internal`, and may be modified by adding the `public`, `fileprivate`, or `private` keyword, as appropriate.

1b. All classes may be subclassed, and all methods, properties, and subscripts overridden. This can be prevented by adding the `final` keyword.

The two concepts are very separate, and I think this is a good thing.

What this proposal suggests, whether or not `final` is kept or removed, is that the two concepts become conflated:

2a. Default access control is `internal`, and may be modified by adding the `public`, `fileprivate`, or `private` keyword, as appropriate.

2b. Non-`public` classes may be subclassed, and non-`public` methods, properties, and subscripts overridden. This can be prevented by adding the `final` keyword.

2c. `public` classes may not be subclassed. To allow this replace the `public` keyword with `subclassable`

2d. `public` methods, properties, and subscripts may not be overridden. To allow this replace the `public` keyword with `overiddable`.

Removing the `final` keyword means a change to 2b above. The first option is simply to throw it away, in which case you end up with:

3a. Default access control is `internal`, and may be modified by adding the `public`, `fileprivate`, or `private` keyword, as appropriate.

3b. Non-`public` classes may be subclassed, and non-`public` methods, properties, and subscripts overridden. This can be never be prevented.

3c. `public` classes may not be subclassed. To allow this replace the `public` keyword with `subclassable`

3d. `public` methods, properties, and subscripts may not be overridden. To allow this replace the `public` keyword with `overiddable`.

The second option is to throw it away, and adjust the default behavior so it truly is `final` as removing the keyword would imply. Then you end up with:

4a. Default access control is `internal`, and may be modified by adding the `public`, `fileprivate`, or `private` keyword, as appropriate.

4b. Non-`public` classes may not be subclassed, and non-`public` methods, properties, and subscripts may not be overridden. This can never be allowed.

4c. `public` classes may not be subclassed. To allow this replace the `public` keyword with `subclassable`

4d. `public` methods, properties, and subscripts may not be overridden. To allow this replace the `public` keyword with `overiddable`.

To me all of these options take a clean, easy-to-understand, language design (1) and turn it into various states of mess. Removing the `final` keyword not only makes it a mess, but removes functionality—the ability to have a mix of final and non-final classes, methods, properties, and subscripts, accessible only within your own module.

Scott

Ok, then I should ask you to consider what I said earlier and substitute "final by default" with "sealed by default" and if we have this sealed keyword not to make it the default.
Trust coders and people a bit more instead of resorting to more involved obligatory processes instead. Do we really need in this case to seal modules by default? Can we just have a keyword that the library author can use and express this intent explicitly?

Of course it can be done either way. But there are significant ecosystem robustness advantages to making sealed the default and comparatively few downsides. Most libraries are open source (so can be modified directly or via PR if necessary) and Apple would likely use sealed widely in any Swift native APIs even if it must be stated explicitly.

Sealed is the Swiftiest default. It makes public API contracts explicit. This is safer, more robust and adds clarity (you don't need to wonder whether an API author intended to support external subclassing, does not want external subclassing but supports needs it internally, or simply hasn't given it any thought)

Usually, look at some countries like Italy in a legislative sense, when you attempt to regulate in a way that everything is kind of banned except what you explicitly allow you get more convoluted, complex, and nerve wrecking solutions than allowing everything except what you explicitly ban (reviewing it all over time of course).

I don't see how comparing the domains of programming language design and politics / economic regulation makes any sense at all.

···

Sent from my iPad

On Jul 9, 2016, at 8:31 AM, Goffredo Marocchi <panajev@gmail.com> wrote:

Sent from my iPhone

On 9 Jul 2016, at 13:36, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Jul 9, 2016, at 3:48 AM, Goffredo Marocchi via swift-evolution <swift-evolution@swift.org> wrote:

Sent from my iPhone

On 8 Jul 2016, at 15:09, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:

Even in Java, it is a bad idea to leave classes subclassable; but having to remember to add final is a chore.

I still think it is worth doing that chore. The fact of the matter is that Java did not and is not enforcing that default and how many widely used production languages you know that do enforce this by default instead of asking library authors to do this bit of work?

People keep talking about just adding final. This *is not* an alternative. We are not talking about preventing subclasses by default (i.e. final by default).

We are talking about preventing subclasses *in other modules* by default (i.e. sealed by default). The alternative would be to introduce a sealed keyword (or similar).

There are times when you *need* to use subclasses inside your module. Some or all of them may not even be directly visible externally (class clusters). However, you *do not* want any new subclasses added as you know that is not likely to end well. This is why having sealed, not just final, is important.

By choosing sealed as a default rather than final, we are keeping the "subclassable by default" status *within* modules. This facilitates experimentation and eliminates the need for application level code to opt-in to subclassing while still making external API contracts explicit and therefore hopefully more robust. It is the default most in-line with the values and goals of Swift.

'final' and 'sealed' are two very different things. Let's please keep this focused on what is actually being proposed.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Sent from my iPhone

What I don't get in the arguments against this capability is the fact that many constructs in Swift can't be subclassed. Are we going to prevent library developers from presenting those in the public API? Your ability to subclass things when not supported by the library developer is already going to be greatly reduced. Additionally you are going to miss potentially helpful optimization in side the model if the library developer can't prevent extras subclassing.

This is one case where the automagical optimisation comes at a cost that you should be knowingly paying for. An explicit keyword that seals the class across module boundaries seems like would do the job just fine.

Also, sorry for the off topic here, libraries are meant to be used by others and developed for others and we should have helping others in mind developing them. We also should be humble enough to admit that we will not be able to explicitly allow/predict all use cases and empowering our users to experiment, report issues, and add features through pull requests. The attitude behind some of the sealed by default reasons I have seen here does not seem it is what we should have in open source software. We do not want a situation where the library author puts the library and not the users on the pedestal.

Nobody is trying to do that. We want an ecosystem that consists of robust, well-designed libraries whose contracts are explicit. Nobody is proposing restricting the ability of a library to expose externally subclassable types.

We are only discussing how to handle cases where the library author has not considered external subclasses in their design and made that consideration explicit by adding the appropriate modifier. I don't see why this is too large a burden to ask of a library author who truly cares about giving users the best experience possible.

We want sealed to be the default precisely because we care *more* about the experience of library users than we do about library authors (specifically, requiring annotation in the library code).

···

Sent from my iPad

On Jul 9, 2016, at 8:37 AM, Goffredo Marocchi <panajev@gmail.com> wrote:

On 9 Jul 2016, at 14:11, Shawn Erickson via swift-evolution <swift-evolution@swift.org> wrote:

It seems perfectly reasonable to allow a lot of freedoms for a library developer when designing their code on their side of the library API and not force them to expose unwanted API just because of internal design desires.

(I have myself have already struggled with having to leak what I consider internal details outside of modules we have developed internally, likely need to get around to outlining the additional issues I see)

In the end if the library isn't good and you don't like the API find one that works the way you need (or make one). I expect a fairly rich environment of libraries that will sort itself out over time.

-Shawn

On Sat, Jul 9, 2016 at 8:43 AM Andre via swift-evolution <swift-evolution@swift.org> wrote:
Hi,

> However, you *do not* want any new subclasses added as you know that is not likely to end well.
Im curious, what kind of real-world scenario would "not end well" cover?

I’m genuinely curious, since Im still on the fence about this, but am willing to be convinced… if sealed by default brings more positives than negatives…

Thanks in advance.

Andre

> 2016/07/09 21:36、Matthew Johnson via swift-evolution <swift-evolution@swift.org> のメール:
>
>
>
> Sent from my iPad
>
>> On Jul 9, 2016, at 3:48 AM, Goffredo Marocchi via swift-evolution <swift-evolution@swift.org> wrote:
>>
>>
>> Sent from my iPhone
>>
>>> On 8 Jul 2016, at 15:09, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:
>>>
>>> Even in Java, it is a bad idea to leave classes subclassable; but having to remember to add final is a chore.
>>
>> I still think it is worth doing that chore. The fact of the matter is that Java did not and is not enforcing that default and how many widely used production languages you know that do enforce this by default instead of asking library authors to do this bit of work?
>
> People keep talking about just adding final. This *is not* an alternative. We are not talking about preventing subclasses by default (i.e. final by default).
>
> We are talking about preventing subclasses *in other modules* by default (i.e. sealed by default). The alternative would be to introduce a sealed keyword (or similar).
>
> There are times when you *need* to use subclasses inside your module. Some or all of them may not even be directly visible externally (class clusters). However, you *do not* want any new subclasses added as you know that is not likely to end well. This is why having sealed, not just final, is important.
>
> By choosing sealed as a default rather than final, we are keeping the "subclassable by default" status *within* modules. This facilitates experimentation and eliminates the need for application level code to opt-in to subclassing while still making external API contracts explicit and therefore hopefully more robust. It is the default most in-line with the values and goals of Swift.
>
> 'final' and 'sealed' are two very different things. Let's please keep this focused on what is actually being proposed.
>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Hi,

However, you *do not* want any new subclasses added as you know that is not likely to end well.

Im curious, what kind of real-world scenario would "not end well" cover?

Any kind of scenario where you need to preserve invariants which could be difficult to preserve if you have to anticipate what 3rd party subclasses might do in their implementations / overrides.

I have seen some comments about nontrivial complexity in Apple’s frameworks caused by apps subclassing where they should not have (i.e. classes that would be sealed if it were possible in Objective-C). This is extremely unfortunate and it impacts everyone on Apple’s platforms.

I wish I had links handy for you, but I don’t recall exactly where or when this was mentioned and don’t have time to dig them up right now.

···

On Jul 9, 2016, at 7:43 AM, Andre <pyunpyun@me.com> wrote:

I’m genuinely curious, since Im still on the fence about this, but am willing to be convinced… if sealed by default brings more positives than negatives…

Thanks in advance.

Andre

2016/07/09 21:36、Matthew Johnson via swift-evolution <swift-evolution@swift.org> のメール:

Sent from my iPad

On Jul 9, 2016, at 3:48 AM, Goffredo Marocchi via swift-evolution <swift-evolution@swift.org> wrote:

Sent from my iPhone

On 8 Jul 2016, at 15:09, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:

Even in Java, it is a bad idea to leave classes subclassable; but having to remember to add final is a chore.

I still think it is worth doing that chore. The fact of the matter is that Java did not and is not enforcing that default and how many widely used production languages you know that do enforce this by default instead of asking library authors to do this bit of work?

People keep talking about just adding final. This *is not* an alternative. We are not talking about preventing subclasses by default (i.e. final by default).

We are talking about preventing subclasses *in other modules* by default (i.e. sealed by default). The alternative would be to introduce a sealed keyword (or similar).

There are times when you *need* to use subclasses inside your module. Some or all of them may not even be directly visible externally (class clusters). However, you *do not* want any new subclasses added as you know that is not likely to end well. This is why having sealed, not just final, is important.

By choosing sealed as a default rather than final, we are keeping the "subclassable by default" status *within* modules. This facilitates experimentation and eliminates the need for application level code to opt-in to subclassing while still making external API contracts explicit and therefore hopefully more robust. It is the default most in-line with the values and goals of Swift.

'final' and 'sealed' are two very different things. Let's please keep this focused on what is actually being proposed.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

What I don't get in the arguments against this capability is the fact that
many constructs in Swift can't be subclassed. Are we going to prevent
library developers from presenting those in the public API? Your ability to
subclass things when not supported by the library developer is already
going to be greatly reduced. Additionally you are going to miss potentially
helpful optimization in side the model if the library developer can't
prevent extras subclassing.

It seems perfectly reasonable to allow a lot of freedoms for a library
developer when designing their code on their side of the library API and
not force them to expose unwanted API just because of internal design
desires.

(I have myself have already struggled with having to leak what I consider
internal details outside of modules we have developed internally, likely
need to get around to outlining the additional issues I see)

In the end if the library isn't good and you don't like the API find one
that works the way you need (or make one). I expect a fairly rich
environment of libraries that will sort itself out over time.

-Shawn

···

On Sat, Jul 9, 2016 at 8:43 AM Andre via swift-evolution < swift-evolution@swift.org> wrote:

Hi,

> However, you *do not* want any new subclasses added as you know that is
not likely to end well.
Im curious, what kind of real-world scenario would "not end well" cover?

I’m genuinely curious, since Im still on the fence about this, but am
willing to be convinced… if sealed by default brings more positives than
negatives…

Thanks in advance.

Andre

> 2016/07/09 21:36、Matthew Johnson via swift-evolution <
swift-evolution@swift.org> のメール:
>
>
>
> Sent from my iPad
>
>> On Jul 9, 2016, at 3:48 AM, Goffredo Marocchi via swift-evolution < > swift-evolution@swift.org> wrote:
>>
>>
>> Sent from my iPhone
>>
>>> On 8 Jul 2016, at 15:09, Károly Lőrentey via swift-evolution < > swift-evolution@swift.org> wrote:
>>>
>>> Even in Java, it is a bad idea to leave classes subclassable; but
having to remember to add final is a chore.
>>
>> I still think it is worth doing that chore. The fact of the matter is
that Java did not and is not enforcing that default and how many widely
used production languages you know that do enforce this by default instead
of asking library authors to do this bit of work?
>
> People keep talking about just adding final. This *is not* an
alternative. We are not talking about preventing subclasses by default
(i.e. final by default).
>
> We are talking about preventing subclasses *in other modules* by default
(i.e. sealed by default). The alternative would be to introduce a sealed
keyword (or similar).
>
> There are times when you *need* to use subclasses inside your module.
Some or all of them may not even be directly visible externally (class
clusters). However, you *do not* want any new subclasses added as you know
that is not likely to end well. This is why having sealed, not just final,
is important.
>
> By choosing sealed as a default rather than final, we are keeping the
"subclassable by default" status *within* modules. This facilitates
experimentation and eliminates the need for application level code to
opt-in to subclassing while still making external API contracts explicit
and therefore hopefully more robust. It is the default most in-line with
the values and goals of Swift.
>
> 'final' and 'sealed' are two very different things. Let's please keep
this focused on what is actually being proposed.
>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

When I expose an API it is to hide details behind it for the benefit of
myself - as the library developer - and to benefit the library user by
hiding complexity and giving them a well testable and tested contract. The
"myself" benefit also benefits consumers of the library since it gives me
the ability to more freely rework things under the hood overtime since I
was able to bound what users of my libraries should have been able to do
(in code dependency terms). I also can limit how much defensive programming
I may need to do.

I don't really see that as an attitude against users of a library.

Also note again I am going to present you with an API that in the swift
realm will have very few subclass able points, it will be protocol based
likely and heavier in structs, etc

Anyway it would be good to separate the votes against closed by default
from those against the ability to close a public class.

-Shawn

···

On Sat, Jul 9, 2016 at 9:37 AM Goffredo Marocchi <panajev@gmail.com> wrote:

Sent from my iPhone

On 9 Jul 2016, at 14:11, Shawn Erickson via swift-evolution < > swift-evolution@swift.org> wrote:

What I don't get in the arguments against this capability is the fact that
many constructs in Swift can't be subclassed. Are we going to prevent
library developers from presenting those in the public API? Your ability to
subclass things when not supported by the library developer is already
going to be greatly reduced. Additionally you are going to miss potentially
helpful optimization in side the model if the library developer can't
prevent extras subclassing.

This is one case where the automagical optimisation comes at a cost that
you should be knowingly paying for. An explicit keyword that seals the
class across module boundaries seems like would do the job just fine.

Also, sorry for the off topic here, libraries are meant to be used by
others and developed for others and we should have helping others in mind
developing them. We also should be humble enough to admit that we will not
be able to explicitly allow/predict all use cases and empowering our users
to experiment, report issues, and add features through pull requests. The
attitude behind some of the sealed by default reasons I have seen here does
not seem it is what we should have in open source software. We do not want
a situation where the library author puts the library and not the users on
the pedestal.

It seems perfectly reasonable to allow a lot of freedoms for a library
developer when designing their code on their side of the library API and
not force them to expose unwanted API just because of internal design
desires.

(I have myself have already struggled with having to leak what I consider
internal details outside of modules we have developed internally, likely
need to get around to outlining the additional issues I see)

In the end if the library isn't good and you don't like the API find one
that works the way you need (or make one). I expect a fairly rich
environment of libraries that will sort itself out over time.

-Shawn
On Sat, Jul 9, 2016 at 8:43 AM Andre via swift-evolution < > swift-evolution@swift.org> wrote:

Hi,

> However, you *do not* want any new subclasses added as you know that is
not likely to end well.
Im curious, what kind of real-world scenario would "not end well" cover?

I’m genuinely curious, since Im still on the fence about this, but am
willing to be convinced… if sealed by default brings more positives than
negatives…

Thanks in advance.

Andre

> 2016/07/09 21:36、Matthew Johnson via swift-evolution <
swift-evolution@swift.org> のメール:
>
>
>
> Sent from my iPad
>
>> On Jul 9, 2016, at 3:48 AM, Goffredo Marocchi via swift-evolution < >> swift-evolution@swift.org> wrote:
>>
>>
>> Sent from my iPhone
>>
>>> On 8 Jul 2016, at 15:09, Károly Lőrentey via swift-evolution < >> swift-evolution@swift.org> wrote:
>>>
>>> Even in Java, it is a bad idea to leave classes subclassable; but
having to remember to add final is a chore.
>>
>> I still think it is worth doing that chore. The fact of the matter is
that Java did not and is not enforcing that default and how many widely
used production languages you know that do enforce this by default instead
of asking library authors to do this bit of work?
>
> People keep talking about just adding final. This *is not* an
alternative. We are not talking about preventing subclasses by default
(i.e. final by default).
>
> We are talking about preventing subclasses *in other modules* by
default (i.e. sealed by default). The alternative would be to introduce a
sealed keyword (or similar).
>
> There are times when you *need* to use subclasses inside your module.
Some or all of them may not even be directly visible externally (class
clusters). However, you *do not* want any new subclasses added as you know
that is not likely to end well. This is why having sealed, not just final,
is important.
>
> By choosing sealed as a default rather than final, we are keeping the
"subclassable by default" status *within* modules. This facilitates
experimentation and eliminates the need for application level code to
opt-in to subclassing while still making external API contracts explicit
and therefore hopefully more robust. It is the default most in-line with
the values and goals of Swift.
>
> 'final' and 'sealed' are two very different things. Let's please keep
this focused on what is actually being proposed.
>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

What I don't get in the arguments against this capability is the fact that many constructs in Swift can't be subclassed. Are we going to prevent library developers from presenting those in the public API? Your ability to subclass things when not supported by the library developer is already going to be greatly reduced. Additionally you are going to miss potentially helpful optimization in side the model if the library developer can't prevent extras subclassing.

This is one case where the automagical optimisation comes at a cost that you should be knowingly paying for. An explicit keyword that seals the class across module boundaries seems like would do the job just fine.

Also, sorry for the off topic here, libraries are meant to be used by others and developed for others and we should have helping others in mind developing them. We also should be humble enough to admit that we will not be able to explicitly allow/predict all use cases and empowering our users to experiment, report issues, and add features through pull requests. The attitude behind some of the sealed by default reasons I have seen here does not seem it is what we should have in open source software. We do not want a situation where the library author puts the library and not the users on the pedestal.

···

Sent from my iPhone

On 9 Jul 2016, at 14:11, Shawn Erickson via swift-evolution <swift-evolution@swift.org> wrote:

It seems perfectly reasonable to allow a lot of freedoms for a library developer when designing their code on their side of the library API and not force them to expose unwanted API just because of internal design desires.

(I have myself have already struggled with having to leak what I consider internal details outside of modules we have developed internally, likely need to get around to outlining the additional issues I see)

In the end if the library isn't good and you don't like the API find one that works the way you need (or make one). I expect a fairly rich environment of libraries that will sort itself out over time.

-Shawn

On Sat, Jul 9, 2016 at 8:43 AM Andre via swift-evolution <swift-evolution@swift.org> wrote:
Hi,

> However, you *do not* want any new subclasses added as you know that is not likely to end well.
Im curious, what kind of real-world scenario would "not end well" cover?

I’m genuinely curious, since Im still on the fence about this, but am willing to be convinced… if sealed by default brings more positives than negatives…

Thanks in advance.

Andre

> 2016/07/09 21:36、Matthew Johnson via swift-evolution <swift-evolution@swift.org> のメール:
>
>
>
> Sent from my iPad
>
>> On Jul 9, 2016, at 3:48 AM, Goffredo Marocchi via swift-evolution <swift-evolution@swift.org> wrote:
>>
>>
>> Sent from my iPhone
>>
>>> On 8 Jul 2016, at 15:09, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:
>>>
>>> Even in Java, it is a bad idea to leave classes subclassable; but having to remember to add final is a chore.
>>
>> I still think it is worth doing that chore. The fact of the matter is that Java did not and is not enforcing that default and how many widely used production languages you know that do enforce this by default instead of asking library authors to do this bit of work?
>
> People keep talking about just adding final. This *is not* an alternative. We are not talking about preventing subclasses by default (i.e. final by default).
>
> We are talking about preventing subclasses *in other modules* by default (i.e. sealed by default). The alternative would be to introduce a sealed keyword (or similar).
>
> There are times when you *need* to use subclasses inside your module. Some or all of them may not even be directly visible externally (class clusters). However, you *do not* want any new subclasses added as you know that is not likely to end well. This is why having sealed, not just final, is important.
>
> By choosing sealed as a default rather than final, we are keeping the "subclassable by default" status *within* modules. This facilitates experimentation and eliminates the need for application level code to opt-in to subclassing while still making external API contracts explicit and therefore hopefully more robust. It is the default most in-line with the values and goals of Swift.
>
> 'final' and 'sealed' are two very different things. Let's please keep this focused on what is actually being proposed.
>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution@swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution