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

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)

First:
The claim about robustness sounds like a fact, despite being just an opinion (feel free to correct me if you have any evidence at all). We should stay honest with our predictions.
Second:
Do you really believe there will be positive impact on open-source libraries?
My forecast is that closed by default will dramatically increase trivial pull request where developers ask for unsealing so that they can do as they like… and I've no idea why somebody could come up with the idea that forking is desirable.

Personally, Im not against sealed by default, but I think there are cases where closed source libraries have certain cases where workarounds are necessary, and just sealing by default will prevent those cases.

One could say, "well just use an open source one, or change vendors" but its not that easy in The Real World™ where we get certain SDKs shoved down our throats by the suits… and while that may be a separate issue to the one at hand, its still a problem that won’t resolve itself by simply locking down things…

In my own case, Ive fought with NSBrowser / NSTreeController in the past and the only way to resolve things was to subclass (and no, waiting 1 or 2 years for a fix is not acceptable if you already have a product in the wild).

So I am reticent to support this proposal without an escape hatch for those cases…

Andre

···

2016/07/09 22:11、Shawn Erickson <shawnce@gmail.com> のメール:

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 <mailto: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 <mailto: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 <mailto: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 <mailto: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 <mailto:swift-evolution@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution

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

To highlight your comment below - I would favor “sealed” being available, I’m not sure I would favor it being the default.

Would it help to perhaps split this into two proposals. First, decide on the issue of sealable being available first and syntax for it. If this passes then a second proposal that examines whether it should be the default?

Best,

Daniel

···

On Jul 10, 2016, at 8:49 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

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

Sent from my iPhone

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

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

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

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.)

I want to point out that this strange behavior can actually be useful:
As of now, you cannot create a public subclass with internal ancestors — it's all or nothing.
When you keep the initializer of the base class internal, you practically seal this class, but it's still possible for framework clients to access the public subclasses supplied for them.

FWIW, if we give up on "by default" for classes, "sealed" could also be a
post-Swift 3 matter here as well. IMO, if the core team finds the reasoning
here persuasive enough to have sealed-by-default for classes, I'd hope for
the same treatment for protocols on that time frame, because as you say
much of the rationale behind the change is analogous for both protocols and
classes.

···

On Mon, Jul 11, 2016 at 11:10 AM, Jordan Rose <jordan_rose@apple.com> wrote:

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).

2016/07/12 6:08、Jordan Rose via swift-evolution <swift-evolution@swift.org> のメッセージ:

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)

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.

This is a very compelling argument and totally logical.

I will change my vote to +0.5 in this case... still not sure about lack of workaround for the off-case but I am in complete agreement with the reasoning of this proposal, thanks for explaining it further...

I guess maybe the thing that bothers some people is that it's forced instead of being a warning/annotation that could be ignored if necessary... of course that definition of "necessary" would be another sticking point >_<.

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.

Yes, this is very good and important point.

It's very appreciated that so many people have taken the time to thoughtfully reply to the questions and talking points, especially Jordan and Mathew! I have learned a lot here...

Andre

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

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

iPadから送信

···

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>:

Thanks for the detailed answer — I didn't expect it.

I'll re-order the original message, since you had a genuine question (to bad for me if it was just a rhetorical one :) whose answer might be more interesting than the pointless remarks afterwards ;-)

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

I guess I would mark everything immature/experimental/whatever on release (as long as it's not to hard to do so…), and then decide step by step wether a method should be public, overridable, private — or removed completely ;-)
Of course, there wouldn't be an universal scale, but most likely each developer/team would keep a reliable standard (some developers are very careful, some are bolder…)
If we are honest, a "sealed" that is only applied because it is the default is actually "experimental" — and in the age of open source, I don't think this should be equivalent to "you can't use this" (some conservative developers consider Swift itself to be immature ;-)
The whole concept is just a spontaneous idea, and maybe I'd come to the conclusion I actually don't like it; but as we can mark stuff as deprecated to notify users that they should stop using a certain method, it might as well be useful to annotate something as unstable to prevent certain developers start using a method (yet).

Now for the remarks — they are pointless indeed, because I'm not expecting to change the mind of anyone with a well-grounded opinion

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.

Imho this is remarkable in two aspects:
It is the first real example I'm aware of where I see a true problem, and it "overthrows" (there might be a less dramatic word I'm not aware of) the position that "sealed" primarily solves a problem for library-authors.

I agree that this problem is much less likely with sealed as default, but there's another one in this scenario:
Client X depends on the later-removed feature in Library A — but as this feature isn't accessible for him, he won't ever start using Library A at all…
None the less, I have to admit that I actually prefer the problem in the sealed-scenario, as it doesn't directly effect developer T, but rather pointy-haired boss Y, who just lost a potential customer ;-)

I can't resist to add another culinary comparison to Jonathan Hull's excellent knife-analogy:
As the owner of a restaurant, would you fill your menu with food that's easy to cook to please your chef, or would you rather include food that tastes fantastic to please your customers?

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.

Damn, maybe I should have put this in front… why make false promises at all, when you just can tell the truth?

Best regards,
Tino

The same argument could be applied to why we have `fileprivate` and `private`, if a class or method is internal to my module, why do I need to mark anything as further private for any reason?

There are a whole bunch of answers; perhaps the module is maintained by a team of twenty people, and the class is intended to be sub-classed within the team, so it’s a level of “internal public.” It doesn’t even have to be a team, maybe I’m writing a utility class that I know I’ll use for years, and I know I’ll forget my rationale later down the line and make the mistake of overriding something I shouldn’t.

Removing `final` is a whole proposal unto itself that I would also -1, it’s not covered by this one, so at this point this is a rabbit hole.

Scott

···

On Jul 6, 2016, at 2:13 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.

I didn’t simply the outlines precisely because the proposal suggests two keywords.

One keyword does solve this problem, but not the problem of conflation of finality and access control.
You end up with this matrix:

  access | can override | final

···

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

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.

-------------+--------------+-------
  open | yes | Error - “class cannot be open and final"
  public | no | Error - “public class is already final by default"
  internal | yes | final
  fileprivate | yes | final
  private | yes | final

This is way more confusing than the current language:

  access | can override | final
-------------+--------------+-------
  public | yes | final
  internal | yes | final
  fileprivate | yes | final
  private | yes | final

I strongly favor a programming language that doesn’t introduce compiler errors to solve problems that could be solved by cleaner syntax.

Since it’s already necessary to place the `public` keyword in front of every class, method, property, or subscript that you intend to make public, the developer is already thinking about the public API. Typing `public final` instead of `public` is an extra keyword, it’s not an extra cognitive burden since that cognition is already taking place.

Scott

This is not true. Public classes will *not* be “final by default”. It *will* be possible to subclass them within their declaring module. If they need to be final they will still need to be marked as such.

With that in mind, the your “can override” column (do you really mean “can subclass” here?) is also not correct. The correct answer is “yes, within the module”. The fundamental difference for this row is that there are some scopes which can *see* the type without the ability to subclass it. There is no problem with this, it is *exactly* what we want.

So would this be more accurate?

  access | can access | can subclass/ | final

···

On Jul 6, 2016, at 2:47 PM, Matthew Johnson <matthew@anandabits.com> wrote:

              > > override where |
-------------+---------------+----------------+-------
  open | all scopes | all scopes | Error
  public | all scopes | within module | final
  internal | within module | within module | final
  fileprivate | within file | within file | final
  private | within scope | within scope | final

The purpose of this proposal is precisely to give library authors the ability to have more fine grained control over what capabilities their library exposes to users.

I don’t have an issue with the purpose, I have an issue with doing it by conflating access control and finality, and making the language confusing as a result.

Assuming the above table matches your expectation, compare it with the same matrix for the language as it is today:

  access | can access | can subclass/ | final
              > > override where |
-------------+---------------+----------------+-------
  public | all scopes | all scopes | final
  internal | within module | within module | final
  fileprivate | within file | within file | final
  private | within scope | within scope | final

The existing table is clean, it’s easy to understand; the two concepts are entirely separate from each other. The access control keyword defines where a class, method, property, or subscript can be accessed from; the `final` keyword defines whether or not it can be subclassed or overridden.

To give you an example of the confusion, here is code made perfectly legal by SE-0025:

  public final class Example {

    overridable func foo() {}

  }

Scott

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.

I didn’t simply the outlines precisely because the proposal suggests two keywords.

One keyword does solve this problem, but not the problem of conflation of finality and access control.
You end up with this matrix:

  access | can override | final
-------------+--------------+-------
  open | yes | Error - “class cannot be open and final”

Of course this produces a compile error. Open means “can subclass” which directly contradicts final.

  public | no | Error - “public class is already final by default”

This is not true. Public classes will *not* be “final by default”. It *will* be possible to subclass them within their declaring module. If they need to be final they will still need to be marked as such.

With that in mind, the your “can override” column (do you really mean “can subclass” here?) is also not correct. The correct answer is “yes, within the module”. The fundamental difference for this row is that there are some scopes which can *see* the type without the ability to subclass it. There is no problem with this, it is *exactly* what we want.

The purpose of this proposal is precisely to give library authors the ability to have more fine grained control over what capabilities their library exposes to users.

  internal | yes | final
  fileprivate | yes | final
  private | yes | final

This is way more confusing than the current language:

  access | can override | final
-------------+--------------+-------
  public | yes | final
  internal | yes | final
  fileprivate | yes | final
  private | yes | final

I strongly favor a programming language that doesn’t introduce compiler errors to solve problems that could be solved by cleaner syntax.

Since it’s already necessary to place the `public` keyword in front of every class, method, property, or subscript that you intend to make public, the developer is already thinking about the public API. Typing `public final` instead of `public` is an extra keyword, it’s not an extra cognitive burden since that cognition is already taking place.

As you can see from the points above, `public final` is something very different than both `public` and `public subclass able` (or `open`) under the current proposal. It is not a question of cleaner syntax. It is a question of whether we want to give library authors more fine grained control.

-Matthew

···

On Jul 6, 2016, at 4:36 PM, Scott James Remnant via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 6, 2016, at 2:13 PM, Leonardo Pessoa <me@lmpessoa.com <mailto:me@lmpessoa.com>> wrote:

Scott

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

This is not true. Public classes will *not* be “final by default”. It *will* be possible to subclass them within their declaring module. If they need to be final they will still need to be marked as such.

With that in mind, the your “can override” column (do you really mean “can subclass” here?) is also not correct. The correct answer is “yes, within the module”. The fundamental difference for this row is that there are some scopes which can *see* the type without the ability to subclass it. There is no problem with this, it is *exactly* what we want.

So would this be more accurate?

  access | can access | can subclass/ | final
              > > override where |
-------------+---------------+----------------+-------
  open | all scopes | all scopes | Error
  public | all scopes | within module | final
  internal | within module | within module | final
  fileprivate | within file | within file | final
  private | within scope | within scope | final

If you want to view “open” as an access modifier, then yes.

The purpose of this proposal is precisely to give library authors the ability to have more fine grained control over what capabilities their library exposes to users.

I don’t have an issue with the purpose, I have an issue with doing it by conflating access control and finality, and making the language confusing as a result.

It’s not conflating access control and finality.

It is also possible to view “open” and “sealed" as part of an “inheritability" hierarchy rather than the access control hierarchy.

final - never subclassable / overridable
sealed (default) - subclassable / overridable *within* the declaring module
open - always subclassable / overridable

In some sense, “open” intersects access control and inheritability: it only makes sense on a public declarations and therefore implies public (whether we allow public to be inferred or not).

Assuming the above table matches your expectation, compare it with the same matrix for the language as it is today:

  access | can access | can subclass/ | final
              > > override where |
-------------+---------------+----------------+-------
  public | all scopes | all scopes | final
  internal | within module | within module | final
  fileprivate | within file | within file | final
  private | within scope | within scope | final

The existing table is clean, it’s easy to understand; the two concepts are entirely separate from each other. The access control keyword defines where a class, method, property, or subscript can be accessed from; the `final` keyword defines whether or not it can be subclassed or overridden.

Many of us believe “final” is too blunt a tool. There are many cases where final cannot be used but you still don’t want external users subclassing or overriding.

We would like a more precise tool for these circumstances and believe if it is going to exist in Swift it should be the default. Its behavior follows the principle of requiring programmers to explicitly make decisions about what behavior is exposed outside of a module. You may not like that principle, but it is one that has been embraced by the language.

To give you an example of the confusion, here is code made perfectly legal by SE-0025:

  public final class Example {

    overridable func foo() {}

  }

I have no idea how you think this is related to SE-0025 (scoped access control). I also don’t understand why you think an `overridable` method in a `final` class would be legal under any proposal. That is nonsense and clearly in error.

-Matthew

···

On Jul 6, 2016, at 5:13 PM, Scott James Remnant <scott@netsplit.com> wrote:

On Jul 6, 2016, at 2:47 PM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:

Scott

I don't disagree with you on 'fileprivate' and 'private' being
unnecessary and we may stick to the proposal at hand and leave the
'final' issue to another proposal, should anyone else care (I myself
don't mind if it sticks around - just have to not use it).

As for the conflict, I don't see it. Can you declare a 'public private
class'? I think it is just the same with 'open' and 'final'. And there
is no need to introduce an error for being redundant with 'public
final' either.

L

···

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

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

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.

I didn’t simply the outlines precisely because the proposal suggests two
keywords.

One keyword does solve this problem, but not the problem of conflation of
finality and access control.
You end up with this matrix:

  access | can override | final
-------------+--------------+-------
  open | yes | Error - “class cannot be open and final"
  public | no | Error - “public class is already final by
default"
  internal | yes | final
  fileprivate | yes | final
  private | yes | final

This is way more confusing than the current language:

  access | can override | final
-------------+--------------+-------
  public | yes | final
  internal | yes | final
  fileprivate | yes | final
  private | yes | final

I strongly favor a programming language that doesn’t introduce compiler
errors to solve problems that could be solved by cleaner syntax.

Since it’s already necessary to place the `public` keyword in front of every
class, method, property, or subscript that you intend to make public, the
developer is already thinking about the public API. Typing `public final`
instead of `public` is an extra keyword, it’s not an extra cognitive burden
since that cognition is already taking place.

Scott

This really isn’t the model for @testable, as evidenced by the fact that top-level names in the testing module still shadow names from the imported module, and that you can refer to the name fully-qualified. Instead, the model is that @testable makes ‘internal' things ‘public'. I think this would make them ‘subclassable’/‘overridable’/‘open’ instead where relevant.

Jordan

···

On Jul 6, 2016, at 09:16, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

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!

Done.

Personally, Im not against sealed by default, but I think there are cases where closed source libraries have certain cases where workarounds are necessary, and just sealing by default will prevent those cases.

One could say, "well just use an open source one, or change vendors" but its not that easy in The Real World™ where we get certain SDKs shoved down our throats by the suits… and while that may be a separate issue to the one at hand, its still a problem that won’t resolve itself by simply locking down things…

In my own case, Ive fought with NSBrowser / NSTreeController in the past and the only way to resolve things was to subclass (and no, waiting 1 or 2 years for a fix is not acceptable if you already have a product in the wild).

So I am reticent to support this proposal without an escape hatch for those cases…

Are you concerned about closed-source vendor frameworks beyond Apple’s? Some things to consider:

1. This proposal should not impact any existing libraries - nobody should be shipping closed-source binary libraries written in Swift yet.

2. Apple’s frameworks will probably remain in Objective-C for some time to come. If / when they are replaced with Swift frameworks the default will have little (if any) impact on the public API contract. It is reasonable to expect that Apple will review the public contracts carefully and add any annotations necessary to achieve the desired semantics.

3. In the future, if you depend on any 3rd party closed-source libraries written in Swift you will be able to ship an update to your app that contains an updated / fixed version of the library independent of the user upgrading their OS.

This leaves the scenario of a case where you depend on a 3rd party, closed-source library written in Swift and where you cannot get (or use) a fix from the vendor for some reason. This is a legitimate concern, but IMO it is not large enough to outweigh all of the advantages of making sealed the default.

There is no doubt that adopting sealed by default will place some pressure on the Swift ecosystem. As others have noted, this pressure already exists in the form of value types, protocol-oriented designs, etc - the current proposal is a relatively modest increase in that pressure. I believe the pressure will have a very positive impact over time (the eventual outcome remains to be seen of course).

Swift library vendors will need to choose between opening their source, providing responsive support and bug fixes, explicitly providing the escape hatch you mention (by designing with open types) or getting a bad reputation among users.

-Matthew

···

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

Andre

2016/07/09 22:11、Shawn Erickson <shawnce@gmail.com <mailto:shawnce@gmail.com>> のメール:

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 <mailto: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 <mailto: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 <mailto: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 <mailto: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 <mailto:swift-evolution@swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution

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

Sent from my iPhone

Second:
Do you really believe there will be positive impact on open-source libraries?
My forecast is that closed by default will dramatically increase trivial pull request where developers ask for unsealing so that they can do as they like…

I think this is a good thing. It will force a considered answer and a discussion about whether or not subclassing should be supported by the library.

So, let's say I ask you to support subclassing in your library, and you say no. What's to stop from just writing something like this:
class YesICan {
   var foo: YouCantInheritThis
   // Duplicate `YouCantInheritThis`'s public API by just passing everything through to `foo`
}

And overloading/extending anything else I need for `YesICan` to, functionally speaking, inherit from `YouCantInheritThis`.

You can certainly do this, but it isn’t equivalent to subclassing for at least two reasons.

First, calls within the methods of YouCantInheritThis will not call into the methods in YesICan. In other words if YouCantInheritThis has both doWork() and okIWill() methods and doWork() calls okIWill(), it will only ever call the implementation of okIWill() in YouCantInheritThis, not the implementation in YesICan. This actually gets to the heart of what one hopes to achieve through final or sealed, specifically avoiding a subclass from inadvertently changing the behavior of a superclass and assumptions the superclass is making about behavior of calls *within that superclass*.

Second, you cannot pass a YesICan where you can pass a YouCantInheritThis, so if you have another library that traffics in YouCantInheritThises, your “subclass” cannot be used with it.

Mark

···

On Jul 9, 2016, at 10:47 AM, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 9, 2016, at 11:29, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

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

Yes, I know it'd all be the epitome of annoying boilerplate code, but my point is that if someone wants to subclass something of yours, there's really not much you can do to stop them.

(Before anyone mentions it, yes, the same trick can be used to get around `final` and `sealed`, but IIRC the motivations there were to enable certain compiler optimizations, not to prohibit "unauthorized" inheritance.)

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

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)

First:
The claim about robustness sounds like a fact, despite being just an opinion (feel free to correct me if you have any evidence at all). We should stay honest with our predictions.
Second:
Do you really believe there will be positive impact on open-source libraries?
My forecast is that closed by default will dramatically increase trivial pull request where developers ask for unsealing so that they can do as they like…

I think this is a good thing. It will force a considered answer and a discussion about whether or not subclassing should be supported by the library.

and I've no idea why somebody could come up with the idea that forking is desirable.

Forking is desirable if your goals, needs, values, etc are substantially different than the library author such that you do not agree on what the API contract should look like.

···

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

Sent from my iPhone

Second:
Do you really believe there will be positive impact on open-source libraries?
My forecast is that closed by default will dramatically increase trivial pull request where developers ask for unsealing so that they can do as they like…

I think this is a good thing. It will force a considered answer and a discussion about whether or not subclassing should be supported by the library.

So, let's say I ask you to support subclassing in your library, and you say no. What's to stop from just writing something like this:
class YesICan {
  var foo: YouCantInheritThis
  // Duplicate `YouCantInheritThis`'s public API by just passing everything through to `foo`
}

And overloading/extending anything else I need for `YesICan` to, functionally speaking, inherit from `YouCantInheritThis`.

You can certainly do this, but it isn’t equivalent to subclassing for at least two reasons.

First, calls within the methods of YouCantInheritThis will not call into the methods in YesICan. In other words if YouCantInheritThis has both doWork() and okIWill() methods and doWork() calls okIWill(), it will only ever call the implementation of okIWill() in YouCantInheritThis, not the implementation in YesICan. This actually gets to the heart of what one hopes to achieve through final or sealed, specifically avoiding a subclass from inadvertently changing the behavior of a superclass and assumptions the superclass is making about behavior of calls *within that superclass*.

Second, you cannot pass a YesICan where you can pass a YouCantInheritThis, so if you have another library that traffics in YouCantInheritThises, your “subclass” cannot be used with it.

What if created a protocol

YouCantInheritThisCloneProtocol{
// everything in sealed class
}

Then you extended the sealed class to conform to the clone protocol.

Changed the other API that you have source to take a Clone Protocol derived object.

This is getting out of hand :) but I think we need a middle ground somewhere.

···

On Jul 9, 2016, at 10:59 AM, Mark Lacey via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 9, 2016, at 10:47 AM, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 9, 2016, at 11:29, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:
On Jul 9, 2016, at 11:04 AM, Tino Heth <2th@gmx.de> wrote:

Mark

Yes, I know it'd all be the epitome of annoying boilerplate code, but my point is that if someone wants to subclass something of yours, there's really not much you can do to stop them.

(Before anyone mentions it, yes, the same trick can be used to get around `final` and `sealed`, but IIRC the motivations there were to enable certain compiler optimizations, not to prohibit "unauthorized" inheritance.)

- Dave Sweeris
_______________________________________________
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

So, let's say I ask you to support subclassing in your library, and you say no. What's to stop from just writing something like this:
  class YesICan {
    var foo: YouCantInheritThis
    // Duplicate `YouCantInheritThis`'s public API by just passing everything through to `foo`
  }

And overloading/extending anything else I need for `YesICan` to, functionally speaking, inherit from `YouCantInheritThis`.

Yes, I know it'd all be the epitome of annoying boilerplate code, but my point is that if someone wants to subclass something of yours, there's really not much you can do to stop them.

(Before anyone mentions it, yes, the same trick can be used to get around `final` and `sealed`, but IIRC the motivations there were to enable certain compiler optimizations, not to prohibit "unauthorized" inheritance.)

- Dave Sweeris

···

Sent from my iPhone

On Jul 9, 2016, at 11:29, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

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

Second:
Do you really believe there will be positive impact on open-source libraries?
My forecast is that closed by default will dramatically increase trivial pull request where developers ask for unsealing so that they can do as they like…

I think this is a good thing. It will force a considered answer and a discussion about whether or not subclassing should be supported by the library.

I fall more in Matthew side of this regarding sealed by default.

···

On Sat, Jul 9, 2016 at 12:29 PM Matthew Johnson via swift-evolution < swift-evolution@swift.org> wrote:

> On Jul 9, 2016, at 11:04 AM, Tino Heth <2th@gmx.de> wrote:
>
>
>> 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)
> First:
> The claim about robustness sounds like a fact, despite being just an
opinion (feel free to correct me if you have any evidence at all). We
should stay honest with our predictions.
> Second:
> Do you really believe there will be positive impact on open-source
libraries?
> My forecast is that closed by default will dramatically increase trivial
pull request where developers ask for unsealing so that they can do as they
like…

I think this is a good thing. It will force a considered answer and a
discussion about whether or not subclassing should be supported by the
library.

> and I've no idea why somebody could come up with the idea that forking
is desirable.

Forking is desirable if your goals, needs, values, etc are substantially
different than the library author such that you do not agree on what the
API contract should look like.

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