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

I believe sealed by default applied to functions makes the behaviour
consistent with classes and allows for the same behaviour we wanted
with this proposal. It would allows us to create subclassable classes
in which we would be able to internally override a method but not
outside the library and selectively allow other methods to be
overriden. Final won't do it so if this is not the default behaviour,
it will be necessary to introduce the sealed keyword so we can achieve
this. It's inconsistent to have to explicitly open a class and
explicitly seal its methods and vice-versa. It was my assumption that
when we chose sealed by default with the proposal we were talking
about everything and not just classes (I at least was talking
everything).

Introducing "dynamic" or some other keyword to mark explicitly methods
that should be overriden is just the same "open" with sealed methods
by default would mean for public methods so it makes no difference to
me. Also having no default will not change that some library
developers will have everything sealed and selectively open. No
default shall also make developers prone to open to think more about
the keyword they'll choose to use, but I'm not fond of no default.

As for the inheritance of openness, I firmly believe it shouldn't. If
a class inherited from an open class is open by default, there will be
no libraries making use of other libraries or we should also introduce
the sealed keyword in order to make the class (or method) sealed
again. This behaviour seems inconsistent to me too.

L

···

On 18 July 2016 at 09:07, Károly Lőrentey <swift-evolution@swift.org> wrote:

On 2016-07-18 09:17:43 +0000, David Hart via swift-evolution said:

On 18 Jul 2016, at 11:11, Xiaodi Wu via swift-evolution >> <swift-evolution@swift.org> wrote:

On Mon, Jul 18, 2016 at 3:27 AM, Brent Royal-Gordon via swift-evolution >> <swift-evolution@swift.org> wrote:
> On Jul 17, 2016, at 8:57 PM, L. Mihalkovic via swift-evolution >> > <swift-evolution@swift.org> wrote:
>
>> On Jul 17, 2016, at 9:14 PM, Garth Snyder via swift-evolution >> >> <swift-evolution@swift.org> wrote:
>>
>> Is there a summary somewhere of the motivation for allowing methods to
>> be declared non-overridable within open classes?
[...]
Garth: I think it's implicit in the reasons to prevent subclassing. The
mere fact that a class allows subclassing doesn't necessarily mean that
every member in it is designed to be subclassed. Consider
`UIViewController`: It's obviously designed to be subclassed, and some
methods in it (such as `loadView`) are intended to be overridden, but others
(such as `loadViewIfNeeded`) are *not* intended to be overridden.

And [if UIViewController were to be written in Swift] there'd be a good
reason why `loadViewIfNeeded` and others of its ilk couldn't be final?

I don't know UIKit internals, but I could imagine loadViewIfNeeded be
overridden internally, if one knows the precise internal workings of
UIViewController. That would require open, to allow overriding internally
but not externally.

I thought about this aspect a little more. I think it's fair to say that
we're breaking new ground for language design here. Classes limiting
inheritance to a certain set of subclasses are nothing new (I've written &
used classes doing this in C++, Java and C#), but no language that I know of
allows limiting overrides of a specific public member in such a way. I think
we need a convincing rationale for making this esoteric middle ground
between final and open members the new default.

The UIKit example above isn't convincing at all. It is already quite easy to
allow package-internal subclasses to configure the behavior of
loadViewIfNeeded without such a novel language feature. E.g., the UIKit team
can simply make loadViewIfNeeded call into a non-final but internal method:

public open class UIViewController {
        private var _view: UIView? = nil

        public final func loadViewIfNeeded() {
                internalLoadViewIfNeeded()
        }

        internal func internalLoadViewIfNeeded() { // overridable internally
                if let view = _view { return }
                loadView()
        }

        public open func loadView() {
                // Load it from a nib or whatevs
        }
}

I see no drawback to this pattern; it is quite clear and simple. Therefore,
in the interest of keeping the language free of needless complexity, I
suggest we change the proposal to remove the implicit "sealed" level of
public member overridability, and support only "open" or "final" class
members.

For members, "open" should mean the opposite of "final", with no levels in
between. Member-level openness should be entirely independent of visibility;
so it should be possible to say "internal open" to mean an internally
overridable member that's not at all visible outside the module -- the same
as today's default.

(Note that (on platforms with an Objective-C runtime) "dynamic" provides a
third level of flexibility for class members; I argue that it should imply
"open". So in order of increasing flexibility, we'd have "final", "open" and
"dynamic" members. This seems easy enough to describe and understand.)

I also suggest that for now, we should make neither "final" nor "open" nor
"dynamic" the default for public members of open classes: we should rather
require class authors to explicity add one of these qualifiers to all public
member declarations. This way, we can defer the argument for choosing a
default to a later (additive) proposal, once we have some experience with
this setup. Non-public members can safely keep defaulting to "internal
open", like they do today.

--
Károly
@lorentey

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

Garth makes an excellent point. Károly is correct that we can already
achieve “sealed” by making a `final` member call through to an `internal`
one.

Therefore, it seem clear that “open” should only be applicable to classes,
not to members. This should simplify the proposal nicely.

Nevin

···

On Mon, Jul 18, 2016 at 2:39 PM, Garth Snyder via swift-evolution < swift-evolution@swift.org> wrote:

> Károly wrote: I suggest we change the proposal to remove the implicit
"sealed" level of public member overridability, and support only "open" or
"final" class members. For members, "open" should mean the opposite of
"final", with no levels in between. Member-level openness should be
entirely independent of visibility; so it should be possible to say
"internal open" to mean an internally overridable member that's not at all
visible outside the module -- the same as today's default.

What is the distinction between this approach and simply omitting the
ability to apply the “open” keyword to anything but a class?

The current behavior is (IIUC) that you cannot override a superclass’s
final method. Aside from that, you can override any other method that’s
visible to you, wherever you stand with regard to the superclass’s origin.
If there’s no sealed status for members, why is any change to member
annotations needed at all?

Garth

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

At the same time, your solution results in a lot of unnecessary boilerplate. Sure, it might be rare with methods, but don’t forget about properties! It makes perfect sense to have properties that should be only overridable internally while being accessible publicly. Imagine adding that boilerplate to every such property..

Basically, if we do it your way, then it won’t be long that someone submits a proposal for a keyword for synthesising the boilerplate, which more or less brings us back to square one.

T.

···

On 18 Jul 2016, at 14:07, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:

I see no drawback to this pattern; it is quite clear and simple. Therefore, in the interest of keeping the language free of needless complexity, I suggest we change the proposal to remove the implicit "sealed" level of public member overridability, and support only "open" or "final" class members.

I believe sealed by default applied to functions makes the behaviour
consistent with classes and allows for the same behaviour we wanted
with this proposal. It would allows us to create subclassable classes
in which we would be able to internally override a method but not
outside the library and selectively allow other methods to be
overriden. Final won't do it so if this is not the default behaviour,
it will be necessary to introduce the sealed keyword so we can achieve
this.

Can you give an example where you'd need such a sealed method, but the obvious pattern I described wouldn't work?

It's inconsistent to have to explicitly open a class and
explicitly seal its methods and vice-versa. It was my assumption that
when we chose sealed by default with the proposal we were talking
about everything and not just classes (I at least was talking
everything).

This was my assumption as well, but then I thought things through. I suspect this might be what our friendly review manager was getting at when he gently lamented the lack of sufficient discussion on "overridable".

Introducing "dynamic" or some other keyword to mark explicitly methods
that should be overriden is just the same "open" with sealed methods
by default would mean for public methods so it makes no difference to
me.

"dynamic" is already in the language. It changes method dispatch to use Objective-C style message passing, enabling advanced techniques based on the dynamic runtime, such as method swizzling or KVO. Since it already exists, I'm not arguing for its introduction; I merely want it integrated into the proposal, in order to keep the language coherent.

Also having no default will not change that some library
developers will have everything sealed and selectively open.
No default shall also make developers prone to open to think more about
the keyword they'll choose to use,

Exactly! For this particular corner of the language, it fulfills the "unwavering goal of requiring additional thought when publishing a class as public API". Forcing people to make an explicit choice is by far the most straightforward way to achieve this. It also has the nice property of being harder to interpret as a value judgement on overridability, which is clearly a topic that gets people's monocles popping. ಠ_ರೃ

but I'm not fond of no default.

If adding an extra qualifier turns out to be an onerous requirement, we can choose a default at any time later, without breaking existing code. It'll probably be easier to do so once we have a little experience in actually using the new language.

As for the inheritance of openness, I firmly believe it shouldn't. If
a class inherited from an open class is open by default, there will be
no libraries making use of other libraries or we should also introduce
the sealed keyword in order to make the class (or method) sealed
again. This behaviour seems inconsistent to me too.

Agreed.

···

On 2016-07-18 16:15:10 +0000, Leonardo Pessoa via swift-evolution said:

L

On 18 July 2016 at 09:07, Károly Lőrentey <swift-evolution@swift.org> wrote:

On 2016-07-18 09:17:43 +0000, David Hart via swift-evolution said:

On 18 Jul 2016, at 11:11, Xiaodi Wu via swift-evolution >>> <swift-evolution@swift.org> wrote:

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

On Jul 17, 2016, at 8:57 PM, L. Mihalkovic via swift-evolution >>>> <swift-evolution@swift.org> wrote:

On Jul 17, 2016, at 9:14 PM, Garth Snyder via swift-evolution >>>>> <swift-evolution@swift.org> wrote:

Is there a summary somewhere of the motivation for allowing methods to
be declared non-overridable within open classes?

[...]
Garth: I think it's implicit in the reasons to prevent subclassing. The
mere fact that a class allows subclassing doesn't necessarily mean that
every member in it is designed to be subclassed. Consider
`UIViewController`: It's obviously designed to be subclassed, and some
methods in it (such as `loadView`) are intended to be overridden, but others
(such as `loadViewIfNeeded`) are *not* intended to be overridden.

And [if UIViewController were to be written in Swift] there'd be a good
reason why `loadViewIfNeeded` and others of its ilk couldn't be final?

I don't know UIKit internals, but I could imagine loadViewIfNeeded be
overridden internally, if one knows the precise internal workings of
UIViewController. That would require open, to allow overriding internally
but not externally.

I thought about this aspect a little more. I think it's fair to say that
we're breaking new ground for language design here. Classes limiting
inheritance to a certain set of subclasses are nothing new (I've written &
used classes doing this in C++, Java and C#), but no language that I know of
allows limiting overrides of a specific public member in such a way. I think
we need a convincing rationale for making this esoteric middle ground
between final and open members the new default.

The UIKit example above isn't convincing at all. It is already quite easy to
allow package-internal subclasses to configure the behavior of
loadViewIfNeeded without such a novel language feature. E.g., the UIKit team
can simply make loadViewIfNeeded call into a non-final but internal method:

public open class UIViewController {
private var _view: UIView? = nil

public final func loadViewIfNeeded() {
internalLoadViewIfNeeded()
}

internal func internalLoadViewIfNeeded() { // overridable internally
if let view = _view { return }
loadView()
}

public open func loadView() {
// Load it from a nib or whatevs
}

I see no drawback to this pattern; it is quite clear and simple. Therefore,
in the interest of keeping the language free of needless complexity, I
suggest we change the proposal to remove the implicit "sealed" level of
public member overridability, and support only "open" or "final" class
members.

For members, "open" should mean the opposite of "final", with no levels in
between. Member-level openness should be entirely independent of visibility;
so it should be possible to say "internal open" to mean an internally
overridable member that's not at all visible outside the module -- the same
as today's default.

(Note that (on platforms with an Objective-C runtime) "dynamic" provides a
third level of flexibility for class members; I argue that it should imply
"open". So in order of increasing flexibility, we'd have "final", "open" and
"dynamic" members. This seems easy enough to describe and understand.)

I also suggest that for now, we should make neither "final" nor "open" nor
"dynamic" the default for public members of open classes: we should rather
require class authors to explicity add one of these qualifiers to all public
member declarations. This way, we can defer the argument for choosing a
default to a later (additive) proposal, once we have some experience with
this setup. Non-public members can safely keep defaulting to "internal
open", like they do today.

--
Károly
@lorentey

_______________________________________________
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

--
Károly
@lorentey

+1 to Karl's suggestion

I've come around to the idea of not allowing subclassing by default (well,
50/50) because I rarely see final used in swift code (read: it's
underused). However, I think the special casing of 3rd party libraries is
weird.

Let's assume this is true: "It's really bad for developers to subclass
things which weren't intended to be subclassed by the developer who wrote
the class."

Given this, we want to turn off subclassability by default (reasonable). So
why is it ok for internal classes to incorrectly subclass things? The main
argument against this seems like it's hard for swift beginners to learn
this rule (but somehow, this proposal isn't?). Or that public APIs are
'more important' (disagree with this generalization. it totally depends on
the library/app. And even if they are more important, if it's better, then
it's better for everything). Am I missing other arguments for why this
would be bad for internal classes?

One other note:

*From and API perspective, *if the developer does everything right and
thinks about subclassing, there is no difference between this proposal and
what we have today. As in, the developer correctly decides to add
final/open (note: I know theres a different in the module itself, but from
an API perspective they are the same).

This proposal only matter when a developer "makes a mistake". It only
applies to developers who forget to take subclassing into consideration.

I've learned that you shouldn't optimize for bad developers. It seems like
adding complexity to the language and a new keyword just so when developers
make a mistake, it's not as bad seems strange to me. I think languages in
general should optimize for good code, not bad.

···

On Wed, Jul 20, 2016 at 4:53 PM ilya via swift-evolution < swift-evolution@swift.org> wrote:

> limiting “open” to public classes only lets you be sloppy inside your
own module.

I don't find that idea bad actually.

In the similar vein one can say "it's ok to be sloppy with local variable
names, it's not ok to be sloppy with instance variable names".

Again, tradeoffs. You can require that things are perfect, so that all
methods are thoughtfully annotated, and all variables have wonderful
self-descriptive names, but you'll get most bang for the buck by requiring
that for the interface (public classes / instance variables) rather than
for the implementation (non-public classes / local variables).

On Thu, Jul 21, 2016 at 12:26 AM, Karl via swift-evolution < > swift-evolution@swift.org> wrote:

> On 20 Jul 2016, at 11:07, Brent Royal-Gordon <brent@architechies.com> >> wrote:
>
>> On Jul 19, 2016, at 6:14 PM, Karl <razielim@gmail.com> wrote:
>>
>> That is to say, we basically introduce “open" as an inverted “final”,
and make all classes non-open by default. That is analogous to enabling
whole-module-optimisation by default, in my opinion. The cost of an extra
four-letter word isn’t that great, but the benefits to readability and
reasonability all-around make it worthwhile.
>
> Okay, but this ignores an important difference between sealed and final.
>
> Sealed is *non-committal*. It makes no promises to wider scopes about
whether there are other subclasses/overrides; it merely states that code
outside the module may not subclass/override. `final`, on the other hand,
is an *affirmative* statement: there are no subclasses/overrides and there
never will be. Code outside the module is permitted to rely on that
fact—for instance, it can generate static calls and conflate dynamic Self
with static Self in conformances.
>
> But this distinction only really makes sense at the `public` boundary,
because that's where the compiler—and even the developer—faces an
impenetrable encapsulation barrier. If you want to require `open` even on
non-public declarations, you thus need to choose one of these designs:
>
> 1. `final` is still available, but only on public APIs.
>
> 2. All non-public classes have to be explicitly declared either
`open` or `final`.
>
> 3. Sealed has some sort of complex, scope-specific design (for
instance, an `internal` class can be subclassed in `fileprivate` but not in
`internal` scope).
>
> Of these three, I think that #2 is overly bureaucratic and #3 is overly
complicated, so only #1 is a viable option. And then we're just choosing
whether we have internal subclassing by default and an odd public-only
`open` keyword, or no internal subclassing by default and an odd
public-only `final` keyword. No viable option avoids some kind of asymmetry
at the `public` boundary.
>
> Open-by-default with a `final` keyword is the traditional design. It is
simpler for people learning to program and more familiar for people new to
Swift. It is more convenient. And you just don't need the same rigorous,
formal definition of semantics in internal scope, where you can alter a
problematic invariant rather than having to live with it.
>
> All that said, there most likely *is* value in declaring, even
internally, which classes are meant to be subclassed and which members are
meant to be overridden. Perhaps what we ought to do is permit `open` even
on non-public APIs, but not enforce it (or rather, leave enforcement to
linters). That would allow people and teams to explicitly document internal
overriding behavior if they want to, without burdening the teams that don't
want to go to the effort.
>
> --
> Brent Royal-Gordon
> Architechies
>

This is exactly what I'm talking about - this is actually a very simple
discussion. Throwing around words like “non-committal” and “affirmative”
and speaking abstractly doesn’t disguise the brunt of what you’re saying:
limiting “open” to public classes only lets you be sloppy inside your own
module. That’s the only reason to make it like that. If I were describing
the concept of “sloppiness” while trying my hardest not to use the word
itself, I would probably say pretty much what you just wrote - wanting to
remain non-committal, avoid definite, affirmative statements, etc.

If we think it is important for people who write classes to locally
reason about their code, it’s important full stop. The conflation between
if it’s publicly accessible and whether or not is a staggeringly massive
regression in read&reason-ability, no matter how matter how you want to
phrase it. It’s a general problem which deserves a general solution - not
some ‘non-committal’ proposal which can't even commit to the problem it is
trying to help with.

If you’ve ever had to deal with complex base classes, consisting of a
mixture of internal (and possibly internally subclassed) members and public
ones, you would know that annotating which members are to be used by what
(and knowing that it’s enforced by the compiler) would be a massive
improvement to our language syntax. Sometimes you didn’t write all the code
you have to work with, so it’s handy to have code which annotates this
stuff.

Karl

_______________________________________________
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

A class that is closed in 1.0 can be opened in 1.1; a class that is `final` in 1.0 *cannot* be opened in 1.1 (or at least it's a breaking change if it is).

Wait a moment: Aren't "closed", "sealed" and "final" basically all the same as soon as you cross module borders? (but I guess it's just that some words are in wrong order…)

Not at all - `closed` and `sealed` are two different names proposed for the same thing but `final` is very different. If you are using a `final` type from a library you (and the compiler) know all variables of that type reference instances of *exactly* that type. You do not get that guarantee with `closed` / `sealed`. In that case a variable may reference a subclass defined in the same module as the type of the variable.

The similarity is that outside the defining module you cannot subclass `final` or `closed` types. But the rest of the semantics are quite different.

···

On Jul 21, 2016, at 3:13 AM, Tino Heth via swift-evolution <swift-evolution@swift.org> wrote:

Am 21.07.2016 um 03:41 schrieb Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org>:

But anyways, the imho important part is between the parenthesis:
Of course you can rename public properties, mark methods as final, rename all your classes or delete the repository of your library easily.
It might break other peoples code and make them angry, but if this is the driving motivation for SE-0117, the whole proposal is a paradox:
Changing the default for subclassability will break other peoples code on a gigantic scale — not only single methods or classes, but nearly every framework.

Just imagine this fear of breaking changes had been applied to Swift… do you think it would have been better for the progress of the language?
I don't think so, and I think that libraries aren't that different in this aspect:
When you start something new, you want breaking changes happen as soon as possible, when there is the most tolerance for them.

As I keep saying for a long time, I think that attitude is an aspect that is terribly neglected here, and imho there are already enough established languages which are pulled down by fear of breaking changes.
Although I have problems sorting out what "swifty" actually means, I might have a longer history as an Apple-customer than most other discussants here, and all marketing aside, there is one distinguishing principle that can be illustrated with many examples:
Better products are preferable over stable products.

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

Oops missed sending to the list.

···

---------- Forwarded message ---------
From: Shawn Erickson <shawnce@gmail.com>
Date: Thu, Jul 21, 2016 at 7:50 AM
Subject: Re: [swift-evolution] [swift-evolution-announce] [Review #2]
SE-0117: Default classes to be non-subclassable publicly
To: Tino Heth <2th@gmx.de>

Swift 3 is going to break code - as you say - on gigantic scale already.
All developers that switch to Swift 3 will have to upgrade all modules they
have and any module developer will have to update their code for Swift 3.
Does this potentially add additional work for a module developer? Yes (some
will get hit harder then others) but it will let them better reason and
state their contract which can save effort longer term and improve
maintainability.

Anyway this is about setting up a language and compiler supported way of
allowing a module developer to more clearly state the API contract for
their module while internally having greater freedom to design things as
make sense. The defaults are being picked to favor being explicit about the
contract which is a good thing for everyone.

As for the rest let's try to keep the discussion in a proposal thread
germane to the proposal and its technical details.

-Shawn

On Thu, Jul 21, 2016 at 4:13 AM Tino Heth via swift-evolution < swift-evolution@swift.org> wrote:

> Am 21.07.2016 um 03:41 schrieb Brent Royal-Gordon via swift-evolution < > swift-evolution@swift.org>:
>
> A class that is closed in 1.0 can be opened in 1.1; a class that is
`final` in 1.0 *cannot* be opened in 1.1 (or at least it's a breaking
change if it is).
Wait a moment: Aren't "closed", "sealed" and "final" basically all the
same as soon as you cross module borders? (but I guess it's just that some
words are in wrong order…)

But anyways, the imho important part is between the parenthesis:
Of course you can rename public properties, mark methods as final, rename
all your classes or delete the repository of your library easily.
It might break other peoples code and make them angry, but if this is the
driving motivation for SE-0117, the whole proposal is a paradox:
Changing the default for subclassability will break other peoples code on
a gigantic scale — not only single methods or classes, but nearly every
framework.

Just imagine this fear of breaking changes had been applied to Swift… do
you think it would have been better for the progress of the language?
I don't think so, and I think that libraries aren't that different in this
aspect:
When you start something new, you want breaking changes happen as soon as
possible, when there is the most tolerance for them.

As I keep saying for a long time, I think that attitude is an aspect that
is terribly neglected here, and imho there are already enough established
languages which are pulled down by fear of breaking changes.
Although I have problems sorting out what "swifty" actually means, I might
have a longer history as an Apple-customer than most other discussants
here, and all marketing aside, there is one distinguishing principle that
can be illustrated with many examples:
Better products are preferable over stable products.

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

>
>
> I see no drawback to this pattern; it is quite clear and simple. Therefore, in the interest of keeping the language free of needless complexity, I suggest we change the proposal to remove the implicit "sealed" level of public member overridability, and support only "open" or "final" class members.

At the same time, your solution results in a lot of unnecessary boilerplate.

It's an exaggeration to say that it's *a lot* of boilerplate. It's one line or two in the base class.

The basic effect of Károly's counter-proposal is that every public member of an open class has to be marked either "open" or "final". That's boilerplate.

I think you and Károly are evaluating the addition of non-open methods as if they were being added primarily to increase expressive capabilities. They do marginally increase expressiveness, but I agree that it's not a common situation to explicitly want. However, neither are non-open classes. The goal here is not to create new expressive power, it's to establish a comprehensible intermediate position that's acceptable as a default so that publicizing an API doesn't require so much annotation and bookkeeping. Otherwise, programmers are forced to immediately decide between over-promising (by making the method publicly overridable) or breaking their own code (if they have internal overrides).

Furthermore, I don't agree that non-open methods add significant new complexity. For clients of a library, a non-open method is final; there are no semantically-detectable differences (ignoring covariant overrides). Within a library, non-open methods remove the need for some unnecessary bookkeeping. And just on a conceptual level, the analogy to class behavior is quite simple.

John.

···

On Jul 18, 2016, at 9:31 AM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:
On Mon, Jul 18, 2016 at 11:24 AM, Taras Zakharko via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
> On 18 Jul 2016, at 14:07, Károly Lőrentey via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Sure, it might be rare with methods, but don’t forget about properties! It makes perfect sense to have properties that should be only overridable internally while being accessible publicly.

My first reaction here was: of course, good point! But then, on reflection, what properties should behave this way? Can you give an example of a property that makes sense to override in internal subclasses but not in external subclasses, but that must be accessible publicly?

Imagine adding that boilerplate to every such property..

On balance, I think the number of `open` annotations would far exceed the amount of this boilerplate. I'm not convinced it is even a mildly common use case.

Basically, if we do it your way, then it won’t be long that someone submits a proposal for a keyword for synthesising the boilerplate, which more or less brings us back to square one.

T.

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

>
>
> I see no drawback to this pattern; it is quite clear and simple.
Therefore, in the interest of keeping the language free of needless
complexity, I suggest we change the proposal to remove the implicit
"sealed" level of public member overridability, and support only "open" or
"final" class members.

At the same time, your solution results in a lot of unnecessary
boilerplate.

It's an exaggeration to say that it's *a lot* of boilerplate. It's one line
or two in the base class.

Sure, it might be rare with methods, but don’t forget about properties! It
makes perfect sense to have properties that should be only overridable
internally while being accessible publicly.

My first reaction here was: of course, good point! But then, on reflection,
what properties should behave this way? Can you give an example of a
property that makes sense to override in internal subclasses but not in
external subclasses, but that must be accessible publicly?

Imagine adding that boilerplate to every such property..

On balance, I think the number of `open` annotations would far exceed the
amount of this boilerplate. I'm not convinced it is even a mildly common
use case.

···

On Mon, Jul 18, 2016 at 11:24 AM, Taras Zakharko via swift-evolution < swift-evolution@swift.org> wrote:

> On 18 Jul 2016, at 14:07, Károly Lőrentey via swift-evolution < > swift-evolution@swift.org> wrote:

Basically, if we do it your way, then it won’t be long that someone
submits a proposal for a keyword for synthesising the boilerplate, which
more or less brings us back to square one.

T.

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

Nevin/Garth, please remember final and sealed are two different
concepts: final prevents anyone from subclassing/overriding while
sealed prevents from subclassing/overriding *outside* the module they
are declared. Thus final is not the same as sealed.

L

···

On 18 July 2016 at 15:45, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

Garth makes an excellent point. Károly is correct that we can already
achieve “sealed” by making a `final` member call through to an `internal`
one.

Therefore, it seem clear that “open” should only be applicable to classes,
not to members. This should simplify the proposal nicely.

Nevin

On Mon, Jul 18, 2016 at 2:39 PM, Garth Snyder via swift-evolution > <swift-evolution@swift.org> wrote:

> Károly wrote: I suggest we change the proposal to remove the implicit
> "sealed" level of public member overridability, and support only "open" or
> "final" class members. For members, "open" should mean the opposite of
> "final", with no levels in between. Member-level openness should be entirely
> independent of visibility; so it should be possible to say "internal open"
> to mean an internally overridable member that's not at all visible outside
> the module -- the same as today's default.

What is the distinction between this approach and simply omitting the
ability to apply the “open” keyword to anything but a class?

The current behavior is (IIUC) that you cannot override a superclass’s
final method. Aside from that, you can override any other method that’s
visible to you, wherever you stand with regard to the superclass’s origin.
If there’s no sealed status for members, why is any change to member
annotations needed at all?

Garth

_______________________________________________
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

I would prefer not to ascribe overridability semantics to `dynamic`. I see `dynamic` as a hint to the compiler that something *outside of normal, safe Swift code* may cause this entity's implementation to change. A `dynamic final` member is a perfectly coherent concept: Formally, nothing is allowed to override this member, but in practice, something may change it behind the compiler's back.

···

On Jul 18, 2016, at 12:06 PM, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:

Introducing "dynamic" or some other keyword to mark explicitly methods
that should be overriden is just the same "open" with sealed methods
by default would mean for public methods so it makes no difference to
me.

"dynamic" is already in the language. It changes method dispatch to use Objective-C style message passing, enabling advanced techniques based on the dynamic runtime, such as method swizzling or KVO. Since it already exists, I'm not arguing for its introduction; I merely want it integrated into the proposal, in order to keep the language coherent.

--
Brent Royal-Gordon
Architechies

The revised proposal is much nicer. I'm still onboard on having classes
sealed by default, but not as much on class members. Initially I felt
that members should follow the same semantics as the class to avoid
confusion, but I've read all the other emails I think I changed my mind.

As demonstrated by Károly, it is pretty simple to create a internally
opened method by having a public final method that calls the internal
one. To me having open only applied to class provides a nice balance to
the language.

···

---

On Mon, Jul 18, 2016, at 03:48, L. Mihalkovic via swift-evolution wrote:

Regards
(From mobile)

On Jul 18, 2016, at 10:27 AM, Brent Royal-Gordon > <brent@architechies.com> > wrote:

On Jul 17, 2016, at 8:57 PM, L. Mihalkovic via swift-evolution <swift- >>> evolution@swift.org> wrote:

On Jul 17, 2016, at 9:14 PM, Garth Snyder via swift-evolution <swift- >>>> evolution@swift.org> wrote:

Is there a summary somewhere of the motivation for allowing methods
to be declared non-overridable within open classes?

Because 1) someone woke up one morning and thought it would be great
2) it goes into the direction of making swift a language for non
programmers 3) the core team wants it

Laurent: This is not a fair characterization of the actual position
of the proposal's supporters. If you can't be civil about this topic,
perhaps you shouldn't be discussing it at all.

3) the core team was very clear that it is the
direction they want for the language.

I have edit the original message to focus only on point number 3.
Garth asked specifically about class members, not class themselves.
The core team "believes with conviction" that classes should not be
open by default but they asked more discussion on the "overridability"
of members.

I believe sealed by default applied to functions makes the behaviour
consistent with classes and allows for the same behaviour we wanted
with this proposal. It would allows us to create subclassable classes
in which we would be able to internally override a method but not
outside the library and selectively allow other methods to be
overriden. Final won't do it so if this is not the default behaviour,
it will be necessary to introduce the sealed keyword so we can achieve
this.

Can you give an example where you'd need such a sealed method, but the
obvious pattern I described wouldn't work?

I'm not saying your pattern doesn't work; I'm saying it requires more code.

It's inconsistent to have to explicitly open a class and
explicitly seal its methods and vice-versa. It was my assumption that
when we chose sealed by default with the proposal we were talking
about everything and not just classes (I at least was talking
everything).

This was my assumption as well, but then I thought things through. I suspect
this might be what our friendly review manager was getting at when he gently
lamented the lack of sufficient discussion on "overridable".

Introducing "dynamic" or some other keyword to mark explicitly methods
that should be overriden is just the same "open" with sealed methods
by default would mean for public methods so it makes no difference to
me.

"dynamic" is already in the language. It changes method dispatch to use
Objective-C style message passing, enabling advanced techniques based on the
dynamic runtime, such as method swizzling or KVO. Since it already exists,
I'm not arguing for its introduction; I merely want it integrated into the
proposal, in order to keep the language coherent.

When I was talking "dynamic" here, I was meaning as in .NET (dynamic
is required to say a method can be overriden). Perhaps I was misguided
here.

Also having no default will not change that some library
developers will have everything sealed and selectively open.
No default shall also make developers prone to open to think more about
the keyword they'll choose to use,

Exactly! For this particular corner of the language, it fulfills the
"unwavering goal of requiring additional thought when publishing a class as
public API". Forcing people to make an explicit choice is by far the most
straightforward way to achieve this. It also has the nice property of being
harder to interpret as a value judgement on overridability, which is clearly
a topic that gets people's monocles popping. ಠ_ರೃ

but I'm not fond of no default.

If adding an extra qualifier turns out to be an onerous requirement, we can
choose a default at any time later, without breaking existing code. It'll
probably be easier to do so once we have a little experience in actually
using the new language.

So should we drop internal by default too?

···

On 18 July 2016 at 16:06, Károly Lőrentey <swift-evolution@swift.org> wrote:

On 2016-07-18 16:15:10 +0000, Leonardo Pessoa via swift-evolution said:

As for the inheritance of openness, I firmly believe it shouldn't. If
a class inherited from an open class is open by default, there will be
no libraries making use of other libraries or we should also introduce
the sealed keyword in order to make the class (or method) sealed
again. This behaviour seems inconsistent to me too.

Agreed.

L

On 18 July 2016 at 09:07, Károly Lőrentey <swift-evolution@swift.org> >> wrote:

On 2016-07-18 09:17:43 +0000, David Hart via swift-evolution said:

On 18 Jul 2016, at 11:11, Xiaodi Wu via swift-evolution >>>> <swift-evolution@swift.org> wrote:

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

On Jul 17, 2016, at 8:57 PM, L. Mihalkovic via swift-evolution >>>>> <swift-evolution@swift.org> wrote:

On Jul 17, 2016, at 9:14 PM, Garth Snyder via swift-evolution >>>>>> <swift-evolution@swift.org> wrote:

Is there a summary somewhere of the motivation for allowing methods to
be declared non-overridable within open classes?

[...]
Garth: I think it's implicit in the reasons to prevent subclassing. The
mere fact that a class allows subclassing doesn't necessarily mean that
every member in it is designed to be subclassed. Consider
`UIViewController`: It's obviously designed to be subclassed, and some
methods in it (such as `loadView`) are intended to be overridden, but
others
(such as `loadViewIfNeeded`) are *not* intended to be overridden.

And [if UIViewController were to be written in Swift] there'd be a good
reason why `loadViewIfNeeded` and others of its ilk couldn't be final?

I don't know UIKit internals, but I could imagine loadViewIfNeeded be
overridden internally, if one knows the precise internal workings of
UIViewController. That would require open, to allow overriding
internally
but not externally.

I thought about this aspect a little more. I think it's fair to say that
we're breaking new ground for language design here. Classes limiting
inheritance to a certain set of subclasses are nothing new (I've written
&
used classes doing this in C++, Java and C#), but no language that I know
of
allows limiting overrides of a specific public member in such a way. I
think
we need a convincing rationale for making this esoteric middle ground
between final and open members the new default.

The UIKit example above isn't convincing at all. It is already quite easy
to
allow package-internal subclasses to configure the behavior of
loadViewIfNeeded without such a novel language feature. E.g., the UIKit
team
can simply make loadViewIfNeeded call into a non-final but internal
method:

public open class UIViewController {
private var _view: UIView? = nil

public final func loadViewIfNeeded() {
internalLoadViewIfNeeded()
}

internal func internalLoadViewIfNeeded() { // overridable internally
if let view = _view { return }
loadView()
}

public open func loadView() {
// Load it from a nib or whatevs
}
}

I see no drawback to this pattern; it is quite clear and simple.
Therefore,
in the interest of keeping the language free of needless complexity, I
suggest we change the proposal to remove the implicit "sealed" level of
public member overridability, and support only "open" or "final" class
members.

For members, "open" should mean the opposite of "final", with no levels
in
between. Member-level openness should be entirely independent of
visibility;
so it should be possible to say "internal open" to mean an internally
overridable member that's not at all visible outside the module -- the
same
as today's default.

(Note that (on platforms with an Objective-C runtime) "dynamic" provides
a
third level of flexibility for class members; I argue that it should
imply
"open". So in order of increasing flexibility, we'd have "final", "open"
and
"dynamic" members. This seems easy enough to describe and understand.)

I also suggest that for now, we should make neither "final" nor "open"
nor
"dynamic" the default for public members of open classes: we should
rather
require class authors to explicity add one of these qualifiers to all
public
member declarations. This way, we can defer the argument for choosing a
default to a later (additive) proposal, once we have some experience with
this setup. Non-public members can safely keep defaulting to "internal
open", like they do today.

--
Károly
@lorentey

_______________________________________________
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

--
Károly
@lorentey

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

If people were generally happy with having public members of open classes overridable by default, then we certainly wouldn't need to have a separate qualifier for that. (Although as "internal" demonstrates, it's nice to have a formal name for such things.)

However, (1) having a default would allow API designers to define public APIs with very little additional thought, and (2) it seems to me we're very very far from consensus on which default would be best. I'd like to avoid arguing even more about the theoretical merits of choosing open vs final vs dynamic by default. (Note that there are highly respectable app developers who honestly consider "open" much too restricting.)

I'm enthusiastic about sealed-by-default classes, but to be honest, I personally have no idea what default (if any) would be best for class members. Ask me again after I've worked with the new classes for a couple of months.

Karoly
@lorentey

···

On 2016-07-18 18:45:14 +0000, Nevin Brackett-Rozinsky via swift-evolution said:

Garth makes an excellent point. Károly is correct that we can already achieve “sealed” by making a `final` member call through to an `internal` one.

Therefore, it seem clear that “open” should only be applicable to classes, not to members. This should simplify the proposal nicely.

Nevin

On Mon, Jul 18, 2016 at 2:39 PM, Garth Snyder via swift-evolution > <swift-evolution@swift.org> wrote:
> Károly wrote: I suggest we change the proposal to remove the implicit "sealed" level of public member overridability, and support only "open" or "final" class members. For members, "open" should mean the opposite of "final", with no levels in between. Member-level openness should be entirely independent of visibility; so it should be possible to say "internal open" to mean an internally overridable member that's not at all visible outside the module -- the same as today's default.

What is the distinction between this approach and simply omitting the ability to apply the “open” keyword to anything but a class?

The current behavior is (IIUC) that you cannot override a superclass’s final method. Aside from that, you can override any other method that’s visible to you, wherever you stand with regard to the superclass’s origin. If there’s no sealed status for members, why is any change to member annotations needed at all?

Garth

Well, boilerplate is boilerplate. I can imagine a number of situations where you’d need it — any time where you can some sort of logical division in your class design, where some functionality relies on internal invariants that are no business of the user while functionality is tweakable. At the same time, after thinking about it for a while, I agree that such cases might be rare:

1. If your class requires a lot of sealed members, then probably there is a better design somewhere that splits that class in multiple components.
2. If your members are overriden internally but sealed publicly, the performance argument seems to be diminished to me — the compiler still needs to do some sort of vtable dispatch if
    the identity of the instance type is not known at compile-time.
3. I did a quick rudimentary regex through AppKit headers looking for stuff like "((n't)|(not)).{1,15}override“ and its true that there seem to be only a few dozens of documented methods where the documentation explicitly warns agains overriding them. I have no idea how reliable the headers are or what the documentation is generated from, but that appears to me to be quite strong evidence
in support of Károly’s and others arguments.

I still think that explicit open is a better design conceptually, as it requires one to grant per-declaration permission instead than per-declaration prohibition, but I agree that it might be a sub optional design from the practical standpoint because of low frequency of sealed members in practice. In retrospect, It might be indeed worth seriously considering making ‚open‘ a class-level annotation only. But then, there are good arguments for having an additional ‚sealed‘ keyword for the relatively rare case where we want to explicitly exclude a method/property from being overridable.

T.

···

On 18 Jul 2016, at 18:31, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

At the same time, your solution results in a lot of unnecessary boilerplate.

It's an exaggeration to say that it's *a lot* of boilerplate. It's one line or two in the base class.

Sure, it might be rare with methods, but don’t forget about properties! It makes perfect sense to have properties that should be only overridable internally while being accessible publicly.

My first reaction here was: of course, good point! But then, on reflection, what properties should behave this way? Can you give an example of a property that makes sense to override in internal subclasses but not in external subclasses, but that must be accessible publicly?

Nope! Our topic is about guiding people towards putting more consideration into *public* APIs.

···

On 2016-07-18 21:42:03 +0000, Leonardo Pessoa via swift-evolution said:

On 18 July 2016 at 16:06, Károly Lőrentey <swift-evolution@swift.org> wrote:

If adding an extra qualifier turns out to be an onerous requirement, we can
choose a default at any time later, without breaking existing code. It'll
probably be easier to do so once we have a little experience in actually
using the new language.

So should we drop internal by default too?

--
Károly
@lorentey

"dynamic final" is prohibited by the current version of compiler. Can you provide an example of a problem that would be solved by allowing it?

···

On 2016-07-19 00:46:21 +0000, Brent Royal-Gordon via swift-evolution said:

On Jul 18, 2016, at 12:06 PM, Károly Lőrentey via swift-evolution >> <swift-evolution@swift.org> wrote:

Introducing "dynamic" or some other keyword to mark explicitly methods
that should be overriden is just the same "open" with sealed methods
by default would mean for public methods so it makes no difference to
me.

"dynamic" is already in the language. It changes method dispatch to use Objective-C style message passing, enabling advanced techniques based on the dynamic runtime, such as method swizzling or KVO. Since it already exists, I'm not arguing for its introduction; I merely want it integrated into the proposal, in order to keep the language coherent.

I would prefer not to ascribe overridability semantics to `dynamic`. I see `dynamic` as a hint to the compiler that something *outside of normal, safe Swift code* may cause this entity's implementation to change. A `dynamic final` member is a perfectly coherent concept: Formally, nothing is allowed to override this member, but in practice, something may change it behind the compiler's back.

--
Károly
@lorentey

The basic effect of Károly's counter-proposal is that every public member of an open class has to be marked either "open" or "final". That's boilerplate.

My primary point was that there is no need for a middle ground between "final" and "open" members.

I want to push my primary point a little more, so let’s forget my secondary suggestion to have no default, and let’s set an implicit choice.

I'd still argue for having no middle ground. “final” seems to be a good default; its effect matches the proposal.

I think you and Károly are evaluating the addition of non-open methods as if they were being added primarily to increase expressive capabilities. They do marginally increase expressiveness, but I agree that it's not a common situation to explicitly want. However, neither are non-open classes.

It's more of an Occam's razor thing. The proposal prevents people from unintentionally exposing a wider API area than they intended. I agree with this wholeheartedly. I just don't believe that we need to add a brand new access level for members to achieve this goal.

Having an implicit "sealed" class level is a much easier sell for me, because it is sometimes desirable to expose a sealed class hierarchy, but Swift doesn't currently support it well -- AFAICT not as an intentional choice, but rather as an unfortunate side-effect of the initializer rules. You could've simply chosen to propose making "final" the default for public classes. Kotlin's troubles mostly wouldn't apply as long as internal classes would remain open, so I'd have supported that too. But rather than this, the proposal is built on top a nice solution to the sealed class problem. Solving it is obviously a good idea, and it is closely related to the goal of the proposal.

There is no such language problem in Swift 2 with sealed methods: an internal open member is sealed by virtue of not being externally visible. It’s straightforward to add a public final trampoline in the rare case when a sealed member should also be made externally callable. I believe the proposal works perfectly well without adding a language feature for this uncommon usecase.

The goal here is not to create new expressive power, it's to establish a comprehensible intermediate position that's acceptable as a default so that publicizing an API doesn't require so much annotation and bookkeeping. Otherwise, programmers are forced to immediately decide between over-promising (by making the method publicly overridable) or breaking their own code (if they have internal overrides).

But making API public should never be done in a hurry. It includes making the API presentable, which involves some amount of refactoring. Granted, if an API has internally overridden methods that the author wants to make public but sealed, then they'd need to refactor these methods. But given how rare this is, and how easy it is to implement the trampoline pattern, is such a trivial refactoring step really too much to ask? (There are a number of much more complicated refactorings involved in making an API public; e.g., it is often the case that a method I want to make public has a parameter or return value with a type that I wish to keep internal.)

I believe that apart from this one little wrinkle, the behavior that SE-0117 proposes can be fully implemented by allowing just "final", "open" and "dynamic" members, with "final" being the default for public members of open classes, and "open" being the default for all other members (including non-open classes).

Is smoothing out that wrinkle worth introducing a whole new default level of member overridability? I think this is worth some more discussion.

Note that if we end up with "final” members by default and it turns out to be the wrong choice, changing the default to sealed would not be a source-breaking change.

Furthermore, I don't agree that non-open methods add significant new complexity. For clients of a library, a non-open method is final; there are no semantically-detectable differences (ignoring covariant overrides). Within a library, non-open methods remove the need for some unnecessary bookkeeping. And just on a conceptual level, the analogy to class behavior is quite simple.

This reminds me: Whether or not we allow the sealed level on methods, I suggest we provide a contextual keyword to (optionally) spell it. A "sealed" keyword is the obvious choice. This would encourage people to use common terminology, and makes it easier to use search engines to find an explanation of the concept. Autogenerated API summaries should add the "sealed" keyword.

We never have to spell "internal", but I think it is still very useful that it exists.

···

On 2016-07-18, at 19:05, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

--
Károly
@lorentey

Oops missed sending to the list.

it's quite easy to hit the wrong button — but actually, the first recipient list was a better fit for the spirit of your motivation ;-)

Swift 3 is going to break code - as you say - on gigantic scale already. All developers that switch to Swift 3 will have to upgrade all modules they have and any module developer will have to update their code for Swift 3. Does this potentially add additional work for a module developer? Yes (some will get hit harder then others)

(hope there's at least general agreement on this…)

but it will let them better reason and state their contract which can save effort longer term and improve maintainability.
Anyway this is about setting up a language and compiler supported way of allowing a module developer to more clearly state the API contract for their module while internally having greater freedom to design things as make sense. The defaults are being picked to favor being explicit about the contract which is a good thing for everyone.

I had an intrinsic motivation for that reply — and this is not to stop SE-0117, which, after all, has already been accepted.
Driven by the same motivation, I'm answering your message as well:
How can you know that anything is "a good thing for everyone"? I can accept a decision that I consider wrong, but not that it is justified with false assumptions (or even lies — seen all that).
There is a huge group of developers who think that the new defaults are no good thing, but absolutely terrible — and that is something supporters of SE-0117 have to accept, even if such tolerance doesn't fit into their mindset.
I'm not even such a big fan of subclassing, but I wholeheartedly oppose this attitude of "we know what's best for everyone": If anyone had a proof that the decision is superior, he could have saved us a whole big discussion… but there is none, so as I advised to live with sealed-by-default, I advise the other camp to be happy about it and stop fortune-telling.

Tino

···

Am 21.07.2016 um 13:52 schrieb Shawn Erickson via swift-evolution <swift-evolution@swift.org>:

Nevin/Garth, please remember final and sealed are two different
concepts: final prevents anyone from subclassing/overriding while
sealed prevents from subclassing/overriding *outside* the module they
are declared. Thus final is not the same as sealed.

No, of course it isn’t. I could well be misguided, but I don’t think I’m disoriented. :-)

Ultimately, the question is whether sealed methods offer any additional utility or advantage beyond that of sealed classes plus final. The existence of “final” is certainly relevant, as it already provides some, but not all, of the features of method-level sealing.

I’d still like to see a really solid use case that requires the full semantics of sealing at the method level.

Garth