'Public' class visibility specifiers


(Joanna Carter) #1

OK, here comes the girl with the big wooden spoon to stir things up a bit :wink:

When it comes to visibilities on classes, why, oh why do we have public vs open *as well as*
the option of marking stuff as final?

Surely, outside the module boundary :

1. public is the same as final ; i.e. you can see it but you can't derive from/override it

2. open is the same as public without final ; you can see it and derive from/override it

Inside the module boundary, there is essentially no difference between public and open.

In fact, open/public is a conflation of concerns.

Both allow public visibility but, mixed in with that is restriction of inheritance. Surely public is good enough for visibility and final is good enough for inheritance restriction?

////////////////
// Module1 file

// MARK: base classes

open class OpenClass
{
聽聽open func test() { }
}

public class PublicClass
{
聽聽public func test() { }
聽聽
聽聽public final func finalTest() { }
}

public final class FinalPublicClass
{
聽聽public final func test() { }
}

// MARK: derived internal classes

class FrameworkDerivedPublicClass : PublicClass
{
聽聽override func test() { }
聽聽
聽聽override func finalTest() { } // error : instance method overrides a 'final' instance method
}

class FrameworkDerivedFinalPublicClass : FinalPublicClass // error : inheritance from a final class
{
聽聽override func test() { } // error : instance method overrides a 'final' instance method
}
///////////////

///////////////
// Module2 file

class OpenSubclass : OpenClass
{
聽聽override func test() { }
}

class PublicSubclass : PublicClass // error : cannot inherit from non-open class 'PublicClass' outside of its defining module
{
聽聽override func test() { } // error : overriding non-open instance method outside of its defining module
聽聽
聽聽override func finalTest() { } // error : method does not override any method from its superclass
}

class FinalPublicSubclass : FinalPublicClass // error : cannot inherit from non-open class 'FinalPublicClass' outside of its defining module
{
聽聽override func test() { } // error : instance method overrides a 'final' instance method
聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽// error : overriding non-open instance method outside of its defining module
}
////////////////

In fact, the test() method in FinalPublicSubclass gives two errors one of which is the same as when declared in FrameworkDerivedFinalPublicClass.

If final is good enough for inside the module boundary, and the same "overriding final method" error appears in both places, do we really need this added complexity?

Surely, if we take public as meaning no inheritance control anywhere :

public class BaseClass
{
聽聽public func test() { }
聽聽
聽聽public final func finalTest() { }
}

And then, either in or out of the module :

class DerivedClass : BaseClass
{
聽聽override func test() { }
聽聽
聽聽override func finalTest() { } // error : instance method overrides a 'final' instance method
}

Or, if BaseClass were marked as final, then inheritance of the whole class is prohibited.

So, I am proposing a reduction in keywords from open, public and final, to just public and final.

路路路

--
Joanna Carter
Carter Consulting


(Vladimir) #2

OK, here comes the girl with the big wooden spoon to stir things up a bit :wink:

When it comes to visibilities on classes, why, oh why do we have public vs open *as well as*
the option of marking stuff as final?

Surely, outside the module boundary :

1. public is the same as final ; i.e. you can see it but you can't derive from/override it

2. open is the same as public without final ; you can see it and derive from/override it

Inside the module boundary, there is essentially no difference between public and open.

In fact, open/public is a conflation of concerns.

Both allow public visibility but, mixed in with that is restriction of inheritance. Surely public is good enough for visibility and final is good enough for inheritance restriction?

////////////////
// Module1 file

// MARK: base classes

open class OpenClass
{
聽聽open func test() { }
}

public class PublicClass
{
聽聽public func test() { }

聽聽public final func finalTest() { }
}

public final class FinalPublicClass
{
聽聽public final func test() { }
}

// MARK: derived internal classes

class FrameworkDerivedPublicClass : PublicClass
{
聽聽override func test() { }

聽聽override func finalTest() { } // error : instance method overrides a 'final' instance method
}

class FrameworkDerivedFinalPublicClass : FinalPublicClass // error : inheritance from a final class
{
聽聽override func test() { } // error : instance method overrides a 'final' instance method
}
///////////////

///////////////
// Module2 file

class OpenSubclass : OpenClass
{
聽聽override func test() { }
}

class PublicSubclass : PublicClass // error : cannot inherit from non-open class 'PublicClass' outside of its defining module
{
聽聽override func test() { } // error : overriding non-open instance method outside of its defining module

聽聽override func finalTest() { } // error : method does not override any method from its superclass
}

class FinalPublicSubclass : FinalPublicClass // error : cannot inherit from non-open class 'FinalPublicClass' outside of its defining module
{
聽聽override func test() { } // error : instance method overrides a 'final' instance method
聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽// error : overriding non-open instance method outside of its defining module
}
////////////////

In fact, the test() method in FinalPublicSubclass gives two errors one of which is the same as when declared in FrameworkDerivedFinalPublicClass.

If final is good enough for inside the module boundary, and the same "overriding final method" error appears in both places, do we really need this added complexity?

Surely, if we take public as meaning no inheritance control anywhere :

public class BaseClass
{
聽聽public func test() { }

聽聽public final func finalTest() { }
}

And then, either in or out of the module :

class DerivedClass : BaseClass
{
聽聽override func test() { }

聽聽override func finalTest() { } // error : instance method overrides a 'final' instance method
}

Or, if BaseClass were marked as final, then inheritance of the whole class is prohibited.

So, I am proposing a reduction in keywords from open, public and final, to just public and final.

With 'public' we can't have subtypes outside of the module, but can have subtypes inside the module. With 'final' you can't have subtype even inside module.
That was the main idea, that your module knows all the possible subtypes of some type.

路路路

On 20.02.2017 19:47, Joanna Carter via swift-evolution wrote:

--
Joanna Carter
Carter Consulting

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


(Slava Pestov) #3

OK, here comes the girl with the big wooden spoon to stir things up a bit :wink:

When it comes to visibilities on classes, why, oh why do we have public vs open *as well as*
the option of marking stuff as final?

Surely, outside the module boundary :

1. public is the same as final ; i.e. you can see it but you can't derive from/override it

There is one important difference, but it is rather obscure. 鈥榝inal鈥 allows a class to conform to protocols where 鈥橲elf鈥 appears in invariant position in requirements. For example, say you have the following:

struct G<T> {}

protocol P {
聽聽func foo(_: G<Self>)
}

class C {} // either inside your module, or elsewhere

The following is not allowed, and produces an error:

extension C : P {
聽聽func foo(_: G<C>) {} // 鈥楽elf鈥 appears in non-parameter, non-result position
}

The reason being that if you have a subclass D of C, the signature of foo() no longer matches the requirement 鈥 the caller expects to pass in a G<D>, not a G<C>. Recall that D < C does not imply G<D> < G<C> in Swift.

Note that even if 鈥楥鈥 is public and not open, we cannot allow the above conformance, because the module that defined 鈥楥鈥 might later on add a new subclass, invalidating the conformance.

If 鈥楥鈥 is final, this is OK though 鈥 we know there will be no other subclasses, so 鈥楽elf鈥 and 鈥楥鈥 are indeed interchangeable.

Also worth noting that removing 鈥榝inal鈥 from a class is going to be an ABI breaking change (and source compatibility too), whereas changing a 鈥榩ublic鈥 class to 鈥榦pen鈥 poses no such difficulty.

I might be in favor of a proposal to just remove 鈥榝inal鈥 altogether, though, leaving us with just open and public. I鈥檓 not sure how much the ability for classes to conform to such protocols matters in practice.

Slava

路路路

On Feb 20, 2017, at 8:47 AM, Joanna Carter via swift-evolution <swift-evolution@swift.org> wrote:

2. open is the same as public without final ; you can see it and derive from/override it

Inside the module boundary, there is essentially no difference between public and open.

In fact, open/public is a conflation of concerns.

Both allow public visibility but, mixed in with that is restriction of inheritance. Surely public is good enough for visibility and final is good enough for inheritance restriction?

////////////////
// Module1 file

// MARK: base classes

open class OpenClass
{
open func test() { }
}

public class PublicClass
{
public func test() { }

public final func finalTest() { }
}

public final class FinalPublicClass
{
public final func test() { }
}

// MARK: derived internal classes

class FrameworkDerivedPublicClass : PublicClass
{
override func test() { }

override func finalTest() { } // error : instance method overrides a 'final' instance method
}

class FrameworkDerivedFinalPublicClass : FinalPublicClass // error : inheritance from a final class
{
override func test() { } // error : instance method overrides a 'final' instance method
}
///////////////

///////////////
// Module2 file

class OpenSubclass : OpenClass
{
override func test() { }
}

class PublicSubclass : PublicClass // error : cannot inherit from non-open class 'PublicClass' outside of its defining module
{
override func test() { } // error : overriding non-open instance method outside of its defining module

override func finalTest() { } // error : method does not override any method from its superclass
}

class FinalPublicSubclass : FinalPublicClass // error : cannot inherit from non-open class 'FinalPublicClass' outside of its defining module
{
override func test() { } // error : instance method overrides a 'final' instance method
聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽// error : overriding non-open instance method outside of its defining module
}
////////////////

In fact, the test() method in FinalPublicSubclass gives two errors one of which is the same as when declared in FrameworkDerivedFinalPublicClass.

If final is good enough for inside the module boundary, and the same "overriding final method" error appears in both places, do we really need this added complexity?

Surely, if we take public as meaning no inheritance control anywhere :

public class BaseClass
{
public func test() { }

public final func finalTest() { }
}

And then, either in or out of the module :

class DerivedClass : BaseClass
{
override func test() { }

override func finalTest() { } // error : instance method overrides a 'final' instance method
}

Or, if BaseClass were marked as final, then inheritance of the whole class is prohibited.

So, I am proposing a reduction in keywords from open, public and final, to just public and final.

--
Joanna Carter
Carter Consulting

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


(Brent Royal-Gordon) #4

We had an enormous, weeks-long, *hugely* contentious debate about whether to introduce `open`, and the end result was that SE-0117 was revised several times and finally approved. It describes the rationale concisely, but pretty well: <https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md>

It doesn't *specifically* address why `final` was kept as well, but essentially, as the Motivation section describes, we wanted non-inheritable to be the default for public classes, so developers would have to specifically choose to open their classes to public subclassing. That meant that having `public` make the class subclassable would not fulfill our goals.

Look. It's not forbidden to re-litigate old design decisions, but if you're going to do it, you should at least study the original proposal and be prepared to either attack flaws in the Motivation section or show why the Proposed Design does not address the problem. Preferably, you should base your argument knowledge from later experience--things we did not know when we reviewed the proposal. You're not saying anything in this thread that wasn't said over and over again in a series of sprawling, stressful near-flamewars last summer.

Please don't tear open old wounds unless you at least have a new treatment to try.

路路路

On Feb 20, 2017, at 8:47 AM, Joanna Carter via swift-evolution <swift-evolution@swift.org> wrote:

When it comes to visibilities on classes, why, oh why do we have public vs open *as well as*
the option of marking stuff as final?

--
Brent Royal-Gordon
Architechies


(Dimitri Racordon) #5

I鈥檇 agree with the proposition of eliminating open.
It does look as an additional layer of complexity for a use case that is imho not so common.

路路路

On 20 Feb 2017, at 18:07, Vladimir.S via swift-evolution <swift-evolution@swift.org<mailto:swift-evolution@swift.org>> wrote:

On 20.02.2017 19:47, Joanna Carter via swift-evolution wrote:
OK, here comes the girl with the big wooden spoon to stir things up a bit :wink:

When it comes to visibilities on classes, why, oh why do we have public vs open *as well as*
the option of marking stuff as final?

Surely, outside the module boundary :

1. public is the same as final ; i.e. you can see it but you can't derive from/override it

2. open is the same as public without final ; you can see it and derive from/override it

Inside the module boundary, there is essentially no difference between public and open.

In fact, open/public is a conflation of concerns.

Both allow public visibility but, mixed in with that is restriction of inheritance. Surely public is good enough for visibility and final is good enough for inheritance restriction?

////////////////
// Module1 file

// MARK: base classes

open class OpenClass
{
open func test() { }
}

public class PublicClass
{
public func test() { }

public final func finalTest() { }
}

public final class FinalPublicClass
{
public final func test() { }
}

// MARK: derived internal classes

class FrameworkDerivedPublicClass : PublicClass
{
override func test() { }

override func finalTest() { } // error : instance method overrides a 'final' instance method
}

class FrameworkDerivedFinalPublicClass : FinalPublicClass // error : inheritance from a final class
{
override func test() { } // error : instance method overrides a 'final' instance method
}
///////////////

///////////////
// Module2 file

class OpenSubclass : OpenClass
{
override func test() { }
}

class PublicSubclass : PublicClass // error : cannot inherit from non-open class 'PublicClass' outside of its defining module
{
override func test() { } // error : overriding non-open instance method outside of its defining module

override func finalTest() { } // error : method does not override any method from its superclass
}

class FinalPublicSubclass : FinalPublicClass // error : cannot inherit from non-open class 'FinalPublicClass' outside of its defining module
{
override func test() { } // error : instance method overrides a 'final' instance method
聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽// error : overriding non-open instance method outside of its defining module
}
////////////////

In fact, the test() method in FinalPublicSubclass gives two errors one of which is the same as when declared in FrameworkDerivedFinalPublicClass.

If final is good enough for inside the module boundary, and the same "overriding final method" error appears in both places, do we really need this added complexity?

Surely, if we take public as meaning no inheritance control anywhere :

public class BaseClass
{
public func test() { }

public final func finalTest() { }
}

And then, either in or out of the module :

class DerivedClass : BaseClass
{
override func test() { }

override func finalTest() { } // error : instance method overrides a 'final' instance method
}

Or, if BaseClass were marked as final, then inheritance of the whole class is prohibited.

So, I am proposing a reduction in keywords from open, public and final, to just public and final.

With 'public' we can't have subtypes outside of the module, but can have subtypes inside the module. With 'final' you can't have subtype even inside module.
That was the main idea, that your module knows all the possible subtypes of some type.

--
Joanna Carter
Carter Consulting

_______________________________________________
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


(Dimitri Racordon) #6

Yes I do.

路路路

On 20 Feb 2017, at 18:53, Joanna Carter <joanna@carterconsulting.org.uk> wrote:

Le 20 f茅vr. 2017 脿 18:21, Dimitri Racordon <Dimitri.Racordon@unige.ch> a 茅crit :

I鈥檇 agree with the proposition of eliminating open.
It does look as an additional layer of complexity for a use case that is imho not so common.

Do you think it's worth a formal proposal?

--
Joanna Carter
Carter Consulting


(Joanna Carter) #7

If you want to limit visibility of anything to within the module, what's wrong with internal?

Final isn't about visibility, it's about restriction of inheritance, whether the class is visible outside the module or not

路路路

Le 20 f茅vr. 2017 脿 18:07, Vladimir.S <svabox@gmail.com> a 茅crit :

With 'public' we can't have subtypes outside of the module, but can have subtypes inside the module. With 'final' you can't have subtype even inside module.
That was the main idea, that your module knows all the possible subtypes of some type.

--
Joanna Carter
Carter Consulting


(Dimitri Racordon) #8

Sorry I pressed the wrong button, and the mail was sent right away.
I was about to add that I could give it a try, or offer my help.


(Joanna Carter) #9

Do you think it's worth a formal proposal?

路路路

Le 20 f茅vr. 2017 脿 18:21, Dimitri Racordon <Dimitri.Racordon@unige.ch> a 茅crit :

I鈥檇 agree with the proposition of eliminating open.
It does look as an additional layer of complexity for a use case that is imho not so common.

--
Joanna Carter
Carter Consulting


(Joanna Carter) #10

Since I still haven't worked out how to submit a proposal, if you know how to, maybe you should take the lead.

If you want to contact me offline during the preparation, please feel free

路路路

Le 20 f茅vr. 2017 脿 19:18, Dimitri Racordon <Dimitri.Racordon@unige.ch> a 茅crit :

Sorry I pressed the wrong button, and the mail was sent right away.
I was about to add that I could give it a try, or offer my help.

--
Joanna Carter
Carter Consulting


(Joanna Carter) #11

I am not advocating removing 'final' from the language ; rather of removing 'open', which is "foreign" to anyone coming from any other language.

My suggestion was to revert 'open' back to 'public' for visibility purposes and to use 'final' as the means of controlling inheritance/overriding that it has always been.

IMO, it is 'open' that is superfluous to requirements.

路路路

Le 21 f茅vr. 2017 脿 10:28, Slava Pestov <spestov@apple.com> a 茅crit :

There is one important difference, but it is rather obscure. 鈥榝inal鈥 allows a class to conform to protocols where 鈥橲elf鈥 appears in invariant position in requirements. For example, say you have the following:


Also worth noting that removing 鈥榝inal鈥 from a class is going to be an ABI breaking change (and source compatibility too), whereas changing a 鈥榩ublic鈥 class to 鈥榦pen鈥 poses no such difficulty.

I might be in favor of a proposal to just remove 鈥榝inal鈥 altogether, though, leaving us with just open and public. I鈥檓 not sure how much the ability for classes to conform to such protocols matters in practice.

--
Joanna Carter
Carter Consulting


(Joanna Carter) #12

Firstly, let me apologise for any hurt caused, but it is quite difficult to carry on with you own projects *and* keep track of everything that is going on is Swift Evolution, especially with the current mailing list format, with interminable repeat quoting of whole threads in a single digest email.

Now, if it was decided that non-inheritable would be the default for classes :

1. that's only true for inheritance outside of a module

2. why leave 'final' in place (apart from Slava's reasons)?

3. if inheritance of classes, which is a form of extension, is restricted outside of a module, why are extensions of a type allowed without restriction ?

Not forgetting that this discussion was started by me as part of a wider discussion of visibility and extensibility specifiers in general :slight_smile:

路路路

Le 21 f茅vr. 2017 脿 12:02, Brent Royal-Gordon <brent@architechies.com> a 茅crit :

On Feb 20, 2017, at 8:47 AM, Joanna Carter via swift-evolution <swift-evolution@swift.org> wrote:

When it comes to visibilities on classes, why, oh why do we have public vs open *as well as*
the option of marking stuff as final?

We had an enormous, weeks-long, *hugely* contentious debate about whether to introduce `open`, and the end result was that SE-0117 was revised several times and finally approved. It describes the rationale concisely, but pretty well: <https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md>

It doesn't *specifically* address why `final` was kept as well, but essentially, as the Motivation section describes, we wanted non-inheritable to be the default for public classes, so developers would have to specifically choose to open their classes to public subclassing. That meant that having `public` make the class subclassable would not fulfill our goals.

Look. It's not forbidden to re-litigate old design decisions, but if you're going to do it, you should at least study the original proposal and be prepared to either attack flaws in the Motivation section or show why the Proposed Design does not address the problem. Preferably, you should base your argument knowledge from later experience--things we did not know when we reviewed the proposal. You're not saying anything in this thread that wasn't said over and over again in a series of sprawling, stressful near-flamewars last summer.

Please don't tear open old wounds unless you at least have a new treatment to try.

--
Joanna Carter
Carter Consulting


(Derrick Ho) #13

I've thought about how to deal with override in a way that is consistent
with the language. Maybe something like this?

// publicly visible but can't be subclassed.
public private(subclass)

// publicly visible and may be subclasses within the module. The default
public internal(subclass)

// publicly visible and may be subclasses by all.
public public(subclass)

We also can not forget how it apples to methods

public private(override)
public internal(override)
public public(override)

路路路

On Mon, Feb 20, 2017 at 2:12 PM Joanna Carter via swift-evolution < swift-evolution@swift.org> wrote:

> Le 20 f茅vr. 2017 脿 19:18, Dimitri Racordon <Dimitri.Racordon@unige.ch> > a 茅crit :
>
> Sorry I pressed the wrong button, and the mail was sent right away.
> I was about to add that I could give it a try, or offer my help.

Since I still haven't worked out how to submit a proposal, if you know how
to, maybe you should take the lead.

If you want to contact me offline during the preparation, please feel free

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


(Brent Royal-Gordon) #14

Now, if it was decided that non-inheritable would be the default for classes :

1. that's only true for inheritance outside of a module

Yes. As SE-0117 explains, the danger of subclassing is that, if the author of the superclass doesn't expect it and design for it, the subclass can accidentally interfere with the superclass's normal operation. It was felt that this problem is most acute with public classes, where potential subclass authors often have little relationship to the superclass's author and may not have access to the superclass's source code at all. Therefore, as a pragmatic trade-off between safety and convenience, subclassing is permitted by default except for `public` classes.

2. why leave 'final' in place (apart from Slava's reasons)?

There were a number of minor reasons like Slava's, but ultimately, it was felt that `final` is helpful both for non-`public` classes (where it documents that the class is never intended to be subclassed) and for `public` APIs (where it documents that there are no non-`public` subclasses, that there will never *be* non-`public` subclasses in later versions of the library, and that therefore method calls can be made statically).

3. if inheritance of classes, which is a form of extension, is restricted outside of a module, why are extensions of a type allowed without restriction ?

Again, this was explained in SE-0117. Extensions cannot override anything (or access anything about the class that isn't publicly available, since we don't have the `extensible` modifier you proposed), so there's no danger that an extension could interfere with the normal operation of the class. That's not true for subclasses, which can override superclass behavior and change it.

路路路

On Feb 21, 2017, at 3:15 AM, Joanna Carter <joanna@carterconsulting.org.uk> wrote:

--
Brent Royal-Gordon
Architechies


(David Waite) #15

There is one important difference, but it is rather obscure. 鈥榝inal鈥 allows a class to conform to protocols where 鈥橲elf鈥 appears in invariant position in requirements. For example, say you have the following:


Also worth noting that removing 鈥榝inal鈥 from a class is going to be an ABI breaking change (and source compatibility too), whereas changing a 鈥榩ublic鈥 class to 鈥榦pen鈥 poses no such difficulty.

I might be in favor of a proposal to just remove 鈥榝inal鈥 altogether, though, leaving us with just open and public. I鈥檓 not sure how much the ability for classes to conform to such protocols matters in practice.

I am not advocating removing 'final' from the language ; rather of removing 'open', which is "foreign" to anyone coming from any other language.

C# has a very similar concept of sealed members. In C++, members cannot be overrided unless they are declared virtual.

C++ however lets you declare the same member in a subclass without it being used by superclass members or when invoked as the super type - this is mostly because code often don鈥檛 use polymorphism in C++ due to the performance and memory impact.

My suggestion was to revert 'open' back to 'public' for visibility purposes and to use 'final' as the means of controlling inheritance/overriding that it has always been.

I鈥檓 not sure if you mean only controlling overridability at the type level, or defaulting to overridable. I can make strong counterarguments for retaining current behavior in both cases.

IMO, it is 'open' that is superfluous to requirements.

Open is not about requirements. Open is about maintaining invariance of state and proper thread safety as required by the implementation of the superclass.

If the goal isn鈥檛 to work within the bounds of a superclass implementation, protocols are a much more appropriate way to describe requirements

-DW

路路路

On Feb 21, 2017, at 3:47 AM, Joanna Carter via swift-evolution <swift-evolution@swift.org> wrote:

Le 21 f茅vr. 2017 脿 10:28, Slava Pestov <spestov@apple.com> a 茅crit :


(David Hart) #16

When it comes to visibilities on classes, why, oh why do we have public vs open *as well as*
the option of marking stuff as final?

We had an enormous, weeks-long, *hugely* contentious debate about whether to introduce `open`, and the end result was that SE-0117 was revised several times and finally approved. It describes the rationale concisely, but pretty well: <https://github.com/apple/swift-evolution/blob/master/proposals/0117-non-public-subclassable-by-default.md>

It doesn't *specifically* address why `final` was kept as well, but essentially, as the Motivation section describes, we wanted non-inheritable to be the default for public classes, so developers would have to specifically choose to open their classes to public subclassing. That meant that having `public` make the class subclassable would not fulfill our goals.

Look. It's not forbidden to re-litigate old design decisions, but if you're going to do it, you should at least study the original proposal and be prepared to either attack flaws in the Motivation section or show why the Proposed Design does not address the problem. Preferably, you should base your argument knowledge from later experience--things we did not know when we reviewed the proposal. You're not saying anything in this thread that wasn't said over and over again in a series of sprawling, stressful near-flamewars last summer.

Please don't tear open old wounds unless you at least have a new treatment to try.

Firstly, let me apologise for any hurt caused, but it is quite difficult to carry on with you own projects *and* keep track of everything that is going on is Swift Evolution, especially with the current mailing list format, with interminable repeat quoting of whole threads in a single digest email.

Now, if it was decided that non-inheritable would be the default for classes :

1. that's only true for inheritance outside of a module

I think that鈥檚 what he meant.

2. why leave 'final' in place (apart from Slava's reasons)?

It鈥檚 debatable, and if something is worth considering for removal its final. I鈥檝e asked that exact question last month in the "final + lazy + fileprivate modifiers" thread got some interesting counter arguments:

Matthew Johnson:
My experience is that it is a very useful communication of intent to future readers of the code.
That aside, I think the criteria for a change are different now. Your criteria were relevant during the big breaking change era of Swift 3. I think the criteria for removing a feature at this point should be: is it causing problems that justify the breaking change required to remove it?

Charlie Monroe:
To me, it's useful a lot. The module doesn't necessarily be a 1KLOC framework - I've been recently refactoring a 90KLOC module and the final keyword was fairly useful since some subclasses used hacks by overriding some vars or methods. This allowed me to look at it from a different perspecitve, make some members final and create better API endpoints for customization.
Not to mention that it allows the compiler to access stored properties directly when they're final - if I recall correctly someone from the core team mentioning that.

Derrik Ho:
I find the final keyword useful when I want to communicate that this class should not be subclassed.
I think the behavior should remain the same since it is useful.

3. if inheritance of classes, which is a form of extension, is restricted outside of a module, why are extensions of a type allowed without restriction ?

Because extensions don鈥檛 have the same pitfalls as subclassing. The Motivation section of the proposal that introduced open that Brent linked has a very detailed explanations of the pitfalls of subclassing.

路路路

On 21 Feb 2017, at 12:15, Joanna Carter via swift-evolution <swift-evolution@swift.org> wrote:

Le 21 f茅vr. 2017 脿 12:02, Brent Royal-Gordon <brent@architechies.com> a 茅crit :

On Feb 20, 2017, at 8:47 AM, Joanna Carter via swift-evolution <swift-evolution@swift.org> wrote:

Not forgetting that this discussion was started by me as part of a wider discussion of visibility and extensibility specifiers in general :slight_smile:

--
Joanna Carter
Carter Consulting

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


(Joanna Carter) #17

The main problem I see with such naming is that, by the use of the word 'subclass', it implies this is about dealing only with classes.

The reality of the Swift world is that it is no longer just classes that are extensible (by subclassing) but, also, extensions that can be written against any type.

Personally, I still balk at the idea of bracketed visibility specifiers.

路路路

Le 21 f茅vr. 2017 脿 02:25, Derrick Ho <wh1pch81n@gmail.com> a 茅crit :

I've thought about how to deal with override in a way that is consistent with the language. Maybe something like this?

// publicly visible but can't be subclassed.
public private(subclass)

// publicly visible and may be subclasses within the module. The default
public internal(subclass)

// publicly visible and may be subclasses by all.
public public(subclass)

We also can not forget how it apples to methods

public private(override)
public internal(override)
public public(override)

--
Joanna Carter
Carter Consulting


(Xiaodi Wu) #18

>
>
>>
>> There is one important difference, but it is rather obscure. 鈥榝inal鈥
allows a class to conform to protocols where 鈥橲elf鈥 appears in invariant
position in requirements. For example, say you have the following:
>>
>> 鈥
>> Also worth noting that removing 鈥榝inal鈥 from a class is going to be an
ABI breaking change (and source compatibility too), whereas changing a
鈥榩ublic鈥 class to 鈥榦pen鈥 poses no such difficulty.
>>
>> I might be in favor of a proposal to just remove 鈥榝inal鈥 altogether,
though, leaving us with just open and public. I鈥檓 not sure how much the
ability for classes to conform to such protocols matters in practice.
>
> I am not advocating removing 'final' from the language ; rather of
removing 'open', which is "foreign" to anyone coming from any other
language.

C# has a very similar concept of sealed members. In C++, members cannot be
overrided unless they are declared virtual.

C++ however lets you declare the same member in a subclass without it
being used by superclass members or when invoked as the super type - this
is mostly because code often don鈥檛 use polymorphism in C++ due to the
performance and memory impact.

Doesn't Kotlin have `open`? I believe it's used slightly differently, but
from the perspective of a user of a third-party library I think the effect
is the same in both languages.

路路路

On Tue, Feb 21, 2017 at 1:20 PM, David Waite via swift-evolution < swift-evolution@swift.org> wrote:

> On Feb 21, 2017, at 3:47 AM, Joanna Carter via swift-evolution < > swift-evolution@swift.org> wrote:
>> Le 21 f茅vr. 2017 脿 10:28, Slava Pestov <spestov@apple.com> a 茅crit :

My suggestion was to revert 'open' back to 'public' for visibility
purposes and to use 'final' as the means of controlling
inheritance/overriding that it has always been.

I鈥檓 not sure if you mean only controlling overridability at the type
level, or defaulting to overridable. I can make strong counterarguments for
retaining current behavior in both cases.

>
> IMO, it is 'open' that is superfluous to requirements.

Open is not about requirements. Open is about maintaining invariance of
state and proper thread safety as required by the implementation of the
superclass.

If the goal isn鈥檛 to work within the bounds of a superclass
implementation, protocols are a much more appropriate way to describe
requirements

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


(Dimitri Racordon) #19

The reality of the Swift world is that it is no longer just classes that are extensible (by subclassing) but, also, extensions that can be written against any type.

I totally agree with that.

It鈥檚 actually something that bothers me with the open access modifier. It introduces an asymmetry on the language and emphasis on classes, while I think Swift do its best not to.

路路路

On 21 Feb 2017, at 09:09, Joanna Carter <joanna@carterconsulting.org.uk<mailto:joanna@carterconsulting.org.uk>> wrote: