[Returned for revision] SE-0117: Default classes to be non-subclassable publicly


(Chris Lattner) #1

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

Hello Swift Community,

The review of "SE-0117: Default classes to be non-subclassable publicly" ran from July 5…11, 2016. As expected, this proposal was extremely polarizing, with valid arguments on both sides. The opinions held by supporters and opposers are held very strongly, and hundreds of emails were generated in a healthy debate about this topic.

The review manager read every post on this topic, and the core team discussed this topic at length. The core team concluded three things:

- First, the core team *agrees with conviction* that it is the right default for public classes to be non-subclassable outside their module, unless they carry some additional indication from the API author that the class was designed to be subclassed.
- Second, there was insufficient discussion about anything *other* than the first point. The proposal also contains the “overridable” concept, which is highly impactful, but lacked significant discussion.
- Third, the core team agrees that the concrete syntax of “subclassable class” is … suboptimal.

On the first point, there are three related arguments against SE-0117:

- First is that clients of Apple frameworks often choose to subclass classes that Apple publicly documents as being “not for subclassing”, as a way of “getting their job done,” typically as a way to work around claimed bugs in Apple frameworks. The core team and others at Apple feel that this argument is analogous to the argument that Swift should “support method swizzling by default”. Swift loves dynamic features, but has already taken a stance against unanticipated method swizzling, by requiring an API author to indicate where they allow method swizzling with the ‘dynamic’ keyword. Almost all classes vended by Apple APIs are subclassable (which isn’t changed by this proposal) so this argument is not compelling to the core team, nor is it consistent with the existing design of Swift. It is also important to note that Cocoa also makes heavy use of delegation (via protocols) which allows client code to customize framework behavior without subclassing.

- Second is that clients of some other public API vended by a non-Apple framework (e.g. a SwiftPM package) may end up in a situation where the framework author didn’t consider subclass-ability, but the client desires it. In this situation, the core team feels that a bigger problem happened: the vendor of the framework did not completely consider the use cases of the framework. This might have happened due to the framework not using sufficient black box unit testing, a failure of the imagination of the designer in terms of use cases, or because they have a bug in their framework that needs unanticipated subclass-ability in order to “get a job done”. Similar to the first point, the core team feels that the language is not the right place to solve this problem. Instead, there is a simple and general solution: communicate with the framework author and get them to add the capabilities that you desire. If they simply need to add subclass-ability, then that is no problem: it is a source-compatible change to a dependency.

- Third is a longer-term meta-concern, wherein a few people are concerned that future pure-Swift APIs will not consider subclass-ability in their design and will accidentally choose-by-omission to prevent subclass-ability on a future pure-Swift API (vended by Apple or otherwise). The core team feels that this is an extremely unlikely situation for several reasons. First of which is that it heavily overlaps the first two concerns. More significantly, any newly-designed and from-scratch APIs that are intended for Swift-only clients will make use of a breadth of abstractions supported by Swift—structs, enums, protocols, classes. The primary reasons to use classes in Swift are subclassability and reference semantics, so the core team feels that the likelihood of accidental omission is small. Likewise, the decision to require every member of a public class to be marked public in Swift indicates a commitment (in line with SE-0117) that expects cross-module API authors to think carefully about the API they are authoring as well as their use cases.

To reiterate, as a summary, the core team *agrees with conviction* that it is the right default for public classes to be non-subclassable outside their module, unless they carry some additional indication by the API author that the class was designed to be subclassed. However, it does not yet have an opinion as to what that concrete syntax is.

------8<-------

To sum this all up, the core team is rejecting this proposal and requesting a revision to change the concrete syntax to “public open class Foo” instead of “subclassable class Foo". This approach satisfies the *unwavering* goal of requiring additional thought when publishing a class as public API, makes subclass-ability orthogonal to access control, and (admittedly as a bit of a swift-evolution process hack) asks the community for an in-depth discussion of the secondary points of the proposal: does it make sense to require every member to be marked as “overridable” in order to be overridden by an open subclass outside of the current module?

The core team appreciates that this is a situation where it is impossible to please everyone, while also recognizing that the challenges faced by developers of pure-Swift code are not exactly analogous to those faced by Objective-C developers. Thank you to the many and diverse opinions and perspectives that have come in as part of this review cycle!

-Chris


(Paul Cantrell) #2

To sum this all up, the core team is … requesting a revision to change the concrete syntax to “public open class Foo” instead of “subclassable class Foo”.

Yes, +1 to “public open Foo” instead of “subclassable Foo”.

Presumably “open” without “public” is an error…?

[The core team] asks the community for an in-depth discussion of the secondary points of the proposal: does it make sense to require every member to be marked as “overridable” in order to be overridden by an open subclass outside of the current module?

Tentative +1 to this, though I’m open to hearing arguments otherwise.

One could make a good case for overridable being the sensible default and require an explicit “final,” just as for internal subclassing. I lean against this for two reasons:

Explicit “overridable” is consistent with the general principle of explicit exposure of public promises in public APIs.
When a class is declared public, its members are still internal by default. By analogy, class-level “open” shouldn’t make members overridable by default either. With a language like Swift that encourages developers to lean on the compiler for verification, setting consistent expectations is crucial, and this behavior seems more consistent to me.

I look at this proposal primarily from the point of view of a library author who is trying to design their API right, and is looking for assistance from the language in achieving that goal. The core team’s proposals seem consistent with this.

This makes me wonder whether we should remove member-level “final” from the language, and require “overridable” (i.e. final methods by default) even for members of non-open classes just for consistency and simplicity. That may be a separate proposal, but does seem like it needs consideration for Swift 3.

Cheers,

Paul

···

On Jul 14, 2016, at 4:39 PM, Chris Lattner <clattner@apple.com> wrote:


(Brent Royal-Gordon) #3

To be clear: You want this discussion to happen in the next review thread, rather than in this thread?

···

On Jul 14, 2016, at 2:39 PM, Chris Lattner <clattner@apple.com> wrote:

asks the community for an in-depth discussion of the secondary points of the proposal: does it make sense to require every member to be marked as “overridable” in order to be overridden by an open subclass outside of the current module?

--
Brent Royal-Gordon
Architechies


(Charles Srstka) #4

Or because the framework was developed in the real world, rather than Elysium, and real-world framework developers just about *never* anticipate every single way someone might use their framework (Indeed, if developers were capable of such a thing, there would be no need for third-party software in the first place).

Charles

···

On Jul 14, 2016, at 4:39 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

- Second is that clients of some other public API vended by a non-Apple framework (e.g. a SwiftPM package) may end up in a situation where the framework author didn’t consider subclass-ability, but the client desires it. In this situation, the core team feels that a bigger problem happened: the vendor of the framework did not completely consider the use cases of the framework. This might have happened due to the framework not using sufficient black box unit testing, a failure of the imagination of the designer in terms of use cases, or because they have a bug in their framework that needs unanticipated subclass-ability in order to “get a job done”.


(Charlie Monroe) #5

To sum this all up, the core team is rejecting this proposal and requesting a revision to change the concrete syntax to “public open class Foo” instead of “subclassable class Foo". This approach satisfies the *unwavering* goal of requiring additional thought when publishing a class as public API, makes subclass-ability orthogonal to access control, and (admittedly as a bit of a swift-evolution process hack) asks the community for an in-depth discussion of the secondary points of the proposal: does it make sense to require every member to be marked as “overridable” in order to be overridden by an open subclass outside of the current module?

+1 on open instead of subclassable.

As per the overridability I'm coming back and forth. There are certainly classes where you want just a few things to be overridable and then there are classes where you want almost everything overridable.

I would personally prefer not overridable by default, but also an addition of a modifier on open that would allow to specify default overridability:

// by default everything is overridable
open(override) class Bar

Alternatives: open(members), open(all)

This, however, requires a new keyword for "closing" a member within the module:

// Not allowed overriding beyond the module.
closed func foo()

BTW I'm not a fan of the keyword "overridable" at all. Imagine that you subclass the class in another module, override the method and expose it for yet another module and keep the overridability:

overridable override func foo()

I'd prefer the open keyword to be reused here.

···

On Jul 14, 2016, at 11:39 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

-Chris

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


(Tino) #6

does it make sense to require every member to be marked as “overridable” in order to be overridden by an open subclass outside of the current module?

Despite of probably being one of the most passionate defenders against adding restrictions on subclassing, I've to accept the clear statement made by the core team, so the imho only consequent answer to this question is requiring overridable on every single method:
- Whatever the perceived benefits of forbidding subclassing are, they are the same for each specific method
- It is the same principle that is applied to access control

For me, "open" has a tiny disadvantage, because I tend to read it as a verb, but it's most likely the best choice, and I recommend to re-use it and drop overridable:
- open is shorter
- open is less confusing
- access control follows the same principle of reuse

If the goal is taken serious, imho it is also questionable wether it is good to allow writing public properties without explicit rights to do so: It's a similar situation (you can read but not write vs. you can call but not override), and there is the proven template of default file access rights in UNIX.


(Haravikk) #7

To sum this all up, the core team is rejecting this proposal and requesting a revision to change the concrete syntax to “public open class Foo” instead of “subclassable class Foo".

Minor point, but if we're going with the idea of public open and public final for declaration, is it worth re-raising the idea of bracketed accessibility modifiers? When fileprivate vs private was being discussed I was heavily in favour of private(file), private(scope) and private(module) (plus private(type) if we get that) to avoid polluting the language with more accessibility-specific keywords. This seems like a good time to mention it again, as we're now looking at even more keyword pollution:

  public(open)
  public(sealed) // Probably not actually defined, but the default for a plain "public" declaration
  public(final)
  private(module) // Replaces internal
  private(file)
  private(scope) // Default for plain private

If we get a private(type) that's seven different keyword combinations for one feature, so I still very much prefer it being reduced to just public/private plus a modifier :wink:

This approach satisfies the *unwavering* goal of requiring additional thought when publishing a class as public API, makes subclass-ability orthogonal to access control, and (admittedly as a bit of a swift-evolution process hack) asks the community for an in-depth discussion of the secondary points of the proposal: does it make sense to require every member to be marked as “overridable” in order to be overridden by an open subclass outside of the current module?

Personally I'm fine with the proposal's original intent; public open gives a clear guarantee that overriding is okay, while plain public produces a warning encouraging developers to contact an API author if they choose to sub-class it anyway, as it may be unsafe. This lets them leave things nominally open, and address only the parts of their API that people actually want to sub-class, after all, predicting every possible use-case is a non-trivial challenge, and I think it's unreasonable to expect it of everyone.

That said, linters should probably highlight public declarations that lack open or final; if Xcode had a less obtrusive, purely informational, "problem" type then I'd recommend that but that's a separate issue I think.

But yeah, I think it's important to avoid trying to force public open or public final only, as that could lead to bad habits with developers just defining whichever they feel is easiest, probably leading to loads of types that end up being final by default. Better IMO to let developers to define things the easy way first, then go back and decide what exactly needs to be sub-classable once any implementation issues have been resolved.

···

On 14 Jul 2016, at 22:39, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:


(David Waite) #8

To sum this all up, the core team is … requesting a revision to change the concrete syntax to “public open class Foo” instead of “subclassable class Foo”.

Yes, +1 to “public open Foo” instead of “subclassable Foo”.

Presumably “open” without “public” is an error…?

[The core team] asks the community for an in-depth discussion of the secondary points of the proposal: does it make sense to require every member to be marked as “overridable” in order to be overridden by an open subclass outside of the current module?

I don’t see a reason for the openness/overridability/virtualness/finalness of a member to be more complex than a boolean. That is, while classes can be final, sealed or open, I believe members should only be “open” or “final”. I don’t see use cases to have members that are only overridable in-module but not by external parties.

As we suspect an API designer to indicate whether a class is overridable or not by third parties and to determine whether members are exposed to third parties, I would expect them to also want to dictate *how* said type is overridable by third parties (e.g. which members are overridable).

Whether we are open by default or final by default allows them to do this, but transitioning from final to open shouldn’t break existing API usage on revision. You won’t have someone accidentally using your library in a way you didn’t intend to support because you forgot to mark a method as ‘open’. Therefore, I lean toward final by default.

This makes me wonder whether we should remove member-level “final” from the language, and require “overridable” (i.e. final methods by default) even for members of non-open classes just for consistency and simplicity. That may be a separate proposal, but does seem like it needs consideration for Swift 3.

In an inheritance chain, we have to decide whether a subclass inherits the method openness. If so, “final override” could still be valid. Otherwise, I agree about removing member-level ‘final'

-DW

···

On Jul 14, 2016, at 4:18 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 14, 2016, at 4:39 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:


(Paul Cantrell) #9

[The core team] asks the community for an in-depth discussion of the secondary points of the proposal: does it make sense to require every member to be marked as “overridable” in order to be overridden by an open subclass outside of the current module?

I don’t see a reason for the openness/overridability/virtualness/finalness of a member to be more complex than a boolean. That is, while classes can be final, sealed or open, I believe members should only be “open” or “final”. I don’t see use cases to have members that are only overridable in-module but not by external parties.

I tend to agree with that. The “overridable internally, final for the public” mode seems like it might have some use, but feels awfully esoteric.

This makes me wonder whether we should remove member-level “final” from the language, and require “overridable” (i.e. final methods by default) even for members of non-open classes just for consistency and simplicity. That may be a separate proposal, but does seem like it needs consideration for Swift 3.

In an inheritance chain, we have to decide whether a subclass inherits the method openness. If so, “final override” could still be valid. Otherwise, I agree about removing member-level ‘final’

My gut feeling is that “final override” is also a bit esoteric — too much to justify keeping a whole keyword in the language.

A compelling use case for either of these allegedly esoteric things could easily sway me.

Cheers,

Paul

···

On Jul 14, 2016, at 5:56 PM, David Waite <david@alkaline-solutions.com> wrote:

On Jul 14, 2016, at 4:18 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jul 14, 2016, at 4:39 PM, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:


(Chris Lattner) #10

I’m not sure what you’re trying to say. I agree that it is clearly the case that a framework author cannot anticipate every single use case of their framework.

However, it is just as clearly the case that “unanticipated subclassability” isn’t a general solution to that problem.

-Chris

···

On Jul 14, 2016, at 10:58 PM, Charles Srstka <cocoadev@charlessoft.com> wrote:

On Jul 14, 2016, at 4:39 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

- Second is that clients of some other public API vended by a non-Apple framework (e.g. a SwiftPM package) may end up in a situation where the framework author didn’t consider subclass-ability, but the client desires it. In this situation, the core team feels that a bigger problem happened: the vendor of the framework did not completely consider the use cases of the framework. This might have happened due to the framework not using sufficient black box unit testing, a failure of the imagination of the designer in terms of use cases, or because they have a bug in their framework that needs unanticipated subclass-ability in order to “get a job done”.

Or because the framework was developed in the real world, rather than Elysium, and real-world framework developers just about *never* anticipate every single way someone might use their framework (Indeed, if developers were capable of such a thing, there would be no need for third-party software in the first place).


(Riley Testut) #11

FWIW, I'm still against this proposal, but since it will be accepted regardless, here are my thoughts:

• Open keyword is significantly better.
• Members should be *open* by default, and final should be opt-in. If you're opening up a class for subclassing, my gut says you should allow the client to do as they wish. If only one or two methods should be overridable, I think delegation (via protocols) is a much better solution.
• I feel like final and open are now *almost* on the same axis, but not quite; open controls subclassability outside module, but final controls it for both. Why not use access control modifiers, such as:

- public(open)
- internal(open) (default)
- fileprivate(open)
- private(open) = final

Then, we could remove the "final" keyword from the language completely, and use access control as normal. I feel like this unifies everything much better (private(open) does seem a little weird though).

···

On Jul 15, 2016, at 1:27 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 14, 2016, at 2:39 PM, Chris Lattner <clattner@apple.com> wrote:

asks the community for an in-depth discussion of the secondary points of the proposal: does it make sense to require every member to be marked as “overridable” in order to be overridden by an open subclass outside of the current module?

To be clear: You want this discussion to happen in the next review thread, rather than in this thread?

--
Brent Royal-Gordon
Architechies

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


(Chris Lattner) #12

Yep, I’ll kick off another review period as soon as the proposal is revised (hopefully today).

-Chris

···

On Jul 14, 2016, at 11:27 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 14, 2016, at 2:39 PM, Chris Lattner <clattner@apple.com> wrote:

asks the community for an in-depth discussion of the secondary points of the proposal: does it make sense to require every member to be marked as “overridable” in order to be overridden by an open subclass outside of the current module?

To be clear: You want this discussion to happen in the next review thread, rather than in this thread?


(Taras Zakharko) #13

I have deliberately kept away from the discussion on this topic, as I didn’t feel like I can contribute in any meaningful way. In all honesty, I have barely used ‚traditional’ OOP paradigms in the last few years at all. While OOP is a useful tool and its great for modelling certain relationship and problems, I agree with the sentiment expressed by the core team that ‚open by default‘ OOP introduces a great deal of complexity for the framework creator, framework client and the compiler alike. Objective-C, with its dynamic message dispatch is a great example of a feature that is very flexible and quite beautiful, but at the same time so inherently unsafe, that it can easily be abused - AND that has been abused extensively.

The fact is, the programming languages we are using currently don’t do a very good job in declaring, describing and enforcing invariants/contracts. More often than not, the invariants are expressed informally (if they are expressed at all). Under such circumstances, more openness means more chance to break things long-term. Until a more general solution can be found (API access roles? state invariants declarations?) anything that makes things more explicit is a good thing in my book.

···

On 15 Jul 2016, at 07:58, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

Or because the framework was developed in the real world, rather than Elysium, and real-world framework developers just about *never* anticipate every single way someone might use their framework (Indeed, if developers were capable of such a thing, there would be no need for third-party software in the first place).

If I am selling a frying pen, I am really not interested in people using it as a hammer :wink: To be honest, I don’t really understand your post. A framework has a particular application domain and caters to certain use cases. It either meets its design goals or not. What does it have to do with need for third-party software? And how does ability to subclass makes it any better?

— T


(Leonardo Pessoa) #14

I'll also stick with "open" for both class and methods and I believe
they should be required for both too. Just as I may not design a class
to be subclassable outside my library, I may not want to allow a
method to be overriden outside the library either (but I may want to
override it inside the library). It also produces a consistent
behaviour (sealed by default): if I have to mark a public class
subclassable, why allow every method in this class to be overridable
by default? If not, there would indeed be necessary the introduction
of the "sealed" keyword to ensure we could close overrides on those
public methods.

I'm also not a fan of "public(open)" because that is how the language
seems poluted to me ("public private(set)" anyone? how is that clean?)
and I tend to avoid those at all cost.

L

···

On 15 July 2016 at 07:46, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

On 14 Jul 2016, at 22:39, Chris Lattner via swift-evolution > <swift-evolution@swift.org> wrote:

To sum this all up, the core team is rejecting this proposal and requesting
a revision to change the concrete syntax to “public open class Foo” instead
of “subclassable class Foo".

Minor point, but if we're going with the idea of public open and public
final for declaration, is it worth re-raising the idea of bracketed
accessibility modifiers? When fileprivate vs private was being discussed I
was heavily in favour of private(file), private(scope) and private(module)
(plus private(type) if we get that) to avoid polluting the language with
more accessibility-specific keywords. This seems like a good time to mention
it again, as we're now looking at even more keyword pollution:

public(open)
public(sealed) // Probably not actually defined, but the default for a plain
"public" declaration
public(final)
private(module) // Replaces internal
private(file)
private(scope) // Default for plain private

If we get a private(type) that's seven different keyword combinations for
one feature, so I still very much prefer it being reduced to just
public/private plus a modifier :wink:

This approach satisfies the *unwavering* goal of requiring additional
thought when publishing a class as public API, makes subclass-ability
orthogonal to access control, and (admittedly as a bit of a swift-evolution
process hack) asks the community for an in-depth discussion of the secondary
points of the proposal: does it make sense to require every member to be
marked as “overridable” in order to be overridden by an open subclass
outside of the current module?

Personally I'm fine with the proposal's original intent; public open gives a
clear guarantee that overriding is okay, while plain public produces a
warning encouraging developers to contact an API author if they choose to
sub-class it anyway, as it may be unsafe. This lets them leave things
nominally open, and address only the parts of their API that people actually
want to sub-class, after all, predicting every possible use-case is a
non-trivial challenge, and I think it's unreasonable to expect it of
everyone.

That said, linters should probably highlight public declarations that lack
open or final; if Xcode had a less obtrusive, purely informational,
"problem" type then I'd recommend that but that's a separate issue I think.

But yeah, I think it's important to avoid trying to force public open or
public final only, as that could lead to bad habits with developers just
defining whichever they feel is easiest, probably leading to loads of types
that end up being final by default. Better IMO to let developers to define
things the easy way first, then go back and decide what exactly needs to be
sub-classable once any implementation issues have been resolved.

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


(L Mihalkovic) #15

Regards
(From mobile)

- Second is that clients of some other public API vended by a non-Apple framework (e.g. a SwiftPM package) may end up in a situation where the framework author didn’t consider subclass-ability, but the client desires it. In this situation, the core team feels that a bigger problem happened: the vendor of the framework did not completely consider the use cases of the framework. This might have happened due to the framework not using sufficient black box unit testing, a failure of the imagination of the designer in terms of use cases, or because they have a bug in their framework that needs unanticipated subclass-ability in order to “get a job done”.

Or because the framework was developed in the real world, rather than Elysium, and real-world framework developers just about *never* anticipate every single way someone might use their framework (Indeed, if developers were capable of such a thing, there would be no need for third-party software in the first place).

I’m not sure what you’re trying to say. I agree that it is clearly the case that a framework author cannot anticipate every single use case of their framework.

However, it is just as clearly the case that “unanticipated subclassability” isn’t a general solution to that problem.

I think seeing it as 'clear' is pure perspective, rather than fact. For instance it might ba apple's perspective, but not red hat's (consider that it took 10 years to be able to delete apps from our devices... indicating that from apple's view point there seems to be a mindset that we cannot do things opportunistically, assuming the consequences of our choices, which does exist in others parts of the technology planet).

···

On Jul 15, 2016, at 8:26 AM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 14, 2016, at 10:58 PM, Charles Srstka <cocoadev@charlessoft.com> wrote:
On Jul 14, 2016, at 4:39 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

-Chris

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


(Karl) #16

I think it makes sense to have it bracketed, but I’m not sure about replacing internal/private/fileprivate.

I’m thinking it should be:

public(open) class XYZ {}
public internal(open) class XYZ {}

Etc

I view it along the same lines as mutability for properties - I.e. “public private(set)”.

In that case, it would make sense to do the same for overridable members. You can only override members of an open class, so it makes sense to just use the “open” name in both places to denote that you may create your own versions of these things. An overriden method “is” an instance of the original method in the same way a derived class “is” an instance of its base class. My overriden “viewDidLoad” or whatever “is” the viewDidLoad for this class.

So to summarise, I think it should look like this (just showing off features, obviously it wouldn’t be okay to have public(open) members in an internal(open) class...):

public internal(open) XYZ {

    public private(set) var nonOverridable : Something
    public(open) var overridable : Something

    public(open) private(set) var getterOverridable : UIColor? // In this case, the subclass can provide a value in its getter (e.g. backgroundColor) -
                                                                                                  // if it doesn’t (I.e. The type is optional), the base class may have a default value stored. I’m not sure… it’s an awkward one.

    public(open) func overridePointForSubclasses() {
        // Don’t you see so many functions named or documented like this? It’s nice that we’ll have them marked-up explicitly.
    }
}

Karl

···

On 15 Jul 2016, at 12:46, Haravikk via swift-evolution <swift-evolution@swift.org> wrote:

On 14 Jul 2016, at 22:39, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

To sum this all up, the core team is rejecting this proposal and requesting a revision to change the concrete syntax to “public open class Foo” instead of “subclassable class Foo".

Minor point, but if we're going with the idea of public open and public final for declaration, is it worth re-raising the idea of bracketed accessibility modifiers? When fileprivate vs private was being discussed I was heavily in favour of private(file), private(scope) and private(module) (plus private(type) if we get that) to avoid polluting the language with more accessibility-specific keywords. This seems like a good time to mention it again, as we're now looking at even more keyword pollution:

  public(open)
  public(sealed) // Probably not actually defined, but the default for a plain "public" declaration
  public(final)
  private(module) // Replaces internal
  private(file)
  private(scope) // Default for plain private

If we get a private(type) that's seven different keyword combinations for one feature, so I still very much prefer it being reduced to just public/private plus a modifier :wink:


(Mike Sanderson) #17

- Second is that clients of some other public API vended by a non-Apple
framework (e.g. a SwiftPM package) may end up in a situation where the
framework author didn’t consider subclass-ability, but the client desires
it. In this situation, the core team feels that a bigger problem happened:
the vendor of the framework did not completely consider the use cases of
the framework. This might have happened due to the framework not using
sufficient black box unit testing, a failure of the imagination of the
designer in terms of use cases, or because they have a bug in their
framework that needs unanticipated subclass-ability in order to “get a job
done”.

Or because the framework was developed in the real world, rather than
Elysium, and real-world framework developers just about *never* anticipate
every single way someone might use their framework (Indeed, if developers
were capable of such a thing, there would be no need for third-party
software in the first place).

I’m not sure what you’re trying to say. I agree that it is clearly the
case that a framework author cannot anticipate every single use case of
their framework.

However, it is just as clearly the case that “unanticipated
subclassability” isn’t a general solution to that problem.

In the case of open-source frameworks, there is a better solution, simply
to fork. Fork, make the alteration, point the dependency manager to the
fork, and if you think it's of general applicability, submit a pull request.

And this does happen all the time. Not just unanticipated use cases, but
general bugs, bugs from a particular way it's used, some tweaks to adjust
some variable, need something fixed to be altered, designers ask for
something to be moved 10 pixels to the left, etc., including things
subclassing wouldn't even be able to fix. I've had to do this more than a
few times, and it actually never occurred to me to make a subclass for this
reason. A proposal that closed off the only way or the best way to handle
these cases would be unwelcome, but this doesn't do that.

It's another topic, but I think having a fork of open-source dependencies
anyway is prudent, as our friends in javascript learned with their recent
Node Package Manager left-pad fiasco. The left-pad fiasco also indicated
that relying on framework authors to be communicative or to be maintaining
their work or even to be not deliberately spiteful, is not good practice.
Fortunately in the case of open-source we do have that control; in the case
of Apple frameworks the assurances offered as part of sending it for
revision are welcome; and if this puts onus on publishers of third-party
close-source frameworks, ok.

-MikeSand

···

On Fri, Jul 15, 2016 at 2:26 AM, Chris Lattner via swift-evolution < swift-evolution@swift.org> wrote:

On Jul 14, 2016, at 10:58 PM, Charles Srstka <cocoadev@charlessoft.com> > wrote:
On Jul 14, 2016, at 4:39 PM, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:

-Chris

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


(Chris Lattner) #18

I think you’re missing my point: if a framework vendor fails to anticipate some use-case, and the affected part of their API is defined in terms of structs and enums, then subclassability is immaterial. Allowing unanticipated subclassability could address some slice of the problem, but it is “clearly” not a general solution to problem of an API vendor providing an insufficient API.

Also keep in mind that many other things in Swift (e.g. methods not being public by default, not being “dynamic” by default) already cut across this. All of this means that while the experiences using Objective-C frameworks can and should inform this discussion, but it does not translate “directly" to Swift.

-Chris

···

On Jul 15, 2016, at 9:36 AM, L. Mihalkovic <laurent.mihalkovic@gmail.com> wrote:

Or because the framework was developed in the real world, rather than Elysium, and real-world framework developers just about *never* anticipate every single way someone might use their framework (Indeed, if developers were capable of such a thing, there would be no need for third-party software in the first place).

I’m not sure what you’re trying to say. I agree that it is clearly the case that a framework author cannot anticipate every single use case of their framework.

However, it is just as clearly the case that “unanticipated subclassability” isn’t a general solution to that problem.

I think seeing it as 'clear' is pure perspective, rather than fact. For instance it might ba apple's perspective, but not red hat's (consider that it took 10 years to be able to delete apps from our devices... indicating that from apple's view point there seems to be a mindset that we cannot do things opportunistically, assuming the consequences of our choices, which does exist in others parts of the technology planet).


(Andre Elder) #19

2016/07/15 16:37、Riley Testut via swift-evolution <swift-evolution@swift.org> のメッセージ:

FWIW, I'm still against this proposal, but since it will be accepted regardless, here are my thoughts:

• Open keyword is significantly better.
• Members should be *open* by default, and final should be opt-in. If you're opening up a class for subclassing, my gut says you should allow the client to do as they wish.

If you have a class that was not open yet, just making it open wouldn't expose any members: it would then just be subclassable.

If the act of making the class open doesn't implicitly make overriding possible, well all you can do then is add methods, and that can be done with extensions anyways, so it's not as useful and makes the public open class {} pattern just by itself not so useful imho... :grimacing:

Also, the keyword 'open' itself may imply 'hey I'm open, please do what you want with my public members', whereas 'subclassable' is more specific in intention... (but that's just me, so n=1 and all that)

TLDR; +1 to the above: simpler is better and defaulting to overridable for public members on an open class is simpler... invariants can be protected by the 'final' keyword.

If we were to default to non-overridable, a more consistent 'open' on the method is preferred over overridable for me... open class, open method... much better imho...

If only one or two methods should be overridable, I think delegation (via protocols) is a much better solution.

Delegation and protocols all the way!

• I feel like final and open are now *almost* on the same axis, but not quite; open controls subclassability outside module, but final controls it for both. Why not use access control modifiers, such as:

- public(open)
- internal(open) (default)
- fileprivate(open)
- private(open) = final

Then, we could remove the "final" keyword from the language completely, and use access control as normal. I feel like this unifies everything much better (private(open) does seem a little weird though).

I think it does feel a little weird, and maybe makes things more complicated... for example, private(open) by itself doesn't tell me at a glance what consumers of the framework will see... maybe this because of the 'open' being there... it feels like just plain 'private' is better... then I can reason, 'oh this is only a private class, I'm the author so I can do what I want here' or 'oh, this ones public but not open'... maybe I'm wrong though.... :upside_down_face:

Thanks,

Andre

···

On Jul 15, 2016, at 1:27 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 14, 2016, at 2:39 PM, Chris Lattner <clattner@apple.com> wrote:

asks the community for an in-depth discussion of the secondary points of the proposal: does it make sense to require every member to be marked as “overridable” in order to be overridden by an open subclass outside of the current module?

To be clear: You want this discussion to happen in the next review thread, rather than in this thread?

--
Brent Royal-Gordon
Architechies

_______________________________________________
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


(Jean-Daniel) #20

Do we really need an open keyword ?

As already said, if open does nothing more than allowing the class to be subclassed, why not simply make the class subclassable if it contains at least one overridable member ?

In case we require an open keyword, what would happen if someone mark a member overridable, but does not make the class open ? Will the compiler emit an error, or make the class implicitly « open » ?

···

Le 16 juil. 2016 à 00:31, Andre Elder via swift-evolution <swift-evolution@swift.org> a écrit :

2016/07/15 16:37、Riley Testut via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> のメッセージ:

FWIW, I'm still against this proposal, but since it will be accepted regardless, here are my thoughts:

• Open keyword is significantly better.
• Members should be *open* by default, and final should be opt-in. If you're opening up a class for subclassing, my gut says you should allow the client to do as they wish.

If you have a class that was not open yet, just making it open wouldn't expose any members: it would then just be subclassable.

If the act of making the class open doesn't implicitly make overriding possible, well all you can do then is add methods, and that can be done with extensions anyways, so it's not as useful and makes the public open class {} pattern just by itself not so useful imho... :grimacing:

Also, the keyword 'open' itself may imply 'hey I'm open, please do what you want with my public members', whereas 'subclassable' is more specific in intention... (but that's just me, so n=1 and all that)

TLDR; +1 to the above: simpler is better and defaulting to overridable for public members on an open class is simpler... invariants can be protected by the 'final' keyword.

If we were to default to non-overridable, a more consistent 'open' on the method is preferred over overridable for me... open class, open method... much better imho...