[Review #3] SE-0117: Allow distinguishing between public access and public overridability

2016/07/22 0:33、Chris Lattner via swift-evolution <swift-evolution@swift.org> のメール:

Hello Swift community,

The third review of "SE-0117: Allow distinguishing between public access and public overridability" begins now and runs through July 25. The proposal is available here:

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

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and contribute to the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  * What is your evaluation of the proposal?

+1 for the first design
(it would be nice to have stored properties on extensions if we couldn't subclass anymore as that is one of the cases that I would subclass something, or uses associated object in objc)

···

----------

I am totally in agreement with Garths’ most lucid and cogent "reframing" of this proposal:

All we are doing now is subdividing public into two separate sublevels, public and open. Just as public subsumes all the privileges of internal, open subsumes public.

Then we can just reframe the gist of it like this:

Before SE-0117
After SE-0117

Subclassable →
public
open

Visible →
public
public
Outside Module
Default →
internal
internal
Inside Module

private
private

This is a lot less controversial and if this is what this proposal really means, I think there would be a lot less resistance to it...

  * Is the problem being addressed significant enough to warrant a change to Swift?

Yes

  * Does this proposal fit well with the feel and direction of Swift?

Yes

  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

Kotlin w/open classes except that it limits subclassing wether inside or outside of a 'module' (basically taking harder stance against subclassing).

I like swifts "middle-way" approach.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Been following this proposal since original discussions, participated in previous reviews, read all the emails in his discussion and toyed a bit more with kotlin to understand it's open/sealed implementation compared to this proposal, also followed a couple conversations/blogs online about this proposal.

More information about the Swift evolution process is available at

  https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Chris Lattner
Review Manager

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

I'm not sure wether the review period is actually over (or better say "turned into an internal discussion", as I haven't seen a result yet ;-), but as nobody jumped onto the "something more holistic"-train, I used my free time at the lake for some thinking…

There are three degrees of freedom, which makes it hard to create a complete table, but in essence, we are talking about this matrix:

Subclassing allowed Subclassing forbidden
Instantiation allowed open (public) final (public final)
Instantiation forbidden abstract internal

As we have two* choices for each aspect, we have four keywords — no matter if we give each combination its own name, or build combinations of them.
I think combinations are less appealing, because there are already established terms for all cells, but your milage may vary.
Swift currently has no clean separation of the concepts, so "final" is actually "public final".
The matrix contains "abstract", and if you put the preoccupation of "everything should be done with protocols!" away, I guess you'll recognize the beauty of symmetry which is disturbed because one modifier is missing (I guess no one argues that we need something like abstract — it is just about how it can be expressed).

This simple table could be everything that is needed… if Swift had only a separation between modules (and as it has been decided to increase the number of levels, that simplification doesn't seem realistic).

So, we have an impressive number of four access levels, and it starts getting complicated to deal with 16 different keywords… but there is already a well-known alternative to model access rights based on the status of the accessor:
File rights in UNIX.
This system can be operated in a way that I wouldn't recommend for a programming language ("func 755 foo()" anyone?), but also in a more explicit form ("group=rwx"…).
The second variant could be adopted for Swift, and that is where it's getting really complicated, because syntax like "module=subclass, public+call func foo()" most likely isn't "swifty" as well…

None the less, this model would bring true orthogonality, and establish a clean separation of concerns.

- Tino

* properties left aside

I think this reasoning is flawed.

If you make any methods overridable outside your module (“open”),
obviously you mean to allow subclassing outside the module. If you have
no open methods, there's absolutely nothing you need to do to “support
subclasses,” and from a design point-of-view, there's no reason to
restrict people from subclassing.

The only reasons I can see for allowing people to prevent non-final
classes from being subclassed outside the module in which they are
defined are:

1. It feels like a nice point of control to have.

2. Marginal performance gains as noted in the proposal

I personally don't find these to be convincing. #1 in particular seems
like a poor way to make language design decisions. If we decide to add
this point of control, I'll justify it to myself in terms of #2.

P.S., I can live with either alternative; it's just important to me that
we understand the situation clearly when evaluating them.

HTH,

···

on Thu Jul 21 2016, Matthew Johnson <swift-evolution@swift.org> wrote:

  * What is your evaluation of the proposal?

+1 to the first design. I think this is a great solution that
balances the many considerations that have been raised on all sides of
this issue. `open` is 2 characters shorter than `public` so
complaints about boilerplate are no longer valid. `internal` is the
“default” - neither `public` nor `open` are privileged as a “default”
for publishing API outside of a module.

I am interested in language enhancements such as exhaustive pattern
matching on classes and protocols which rely on knowledge of the full
class hierarchy. Such enhancements will be far more useful if the
language supports non-open, non-final classes.

There are design techniques that would require additional boilerplate
if we cannot have non-open, non-final classes.

Most importantly, requiring library authors to choose `public` or
`open` provides important documentation value. Users of the library
will know whether the author intends to support subclasses or not.

--
Dave

  * What is your evaluation of the proposal?

+1 to the first design. I think this is a great solution that
balances the many considerations that have been raised on all sides of
this issue. `open` is 2 characters shorter than `public` so
complaints about boilerplate are no longer valid. `internal` is the
“default” - neither `public` nor `open` are privileged as a “default”
for publishing API outside of a module.

I am interested in language enhancements such as exhaustive pattern
matching on classes and protocols which rely on knowledge of the full
class hierarchy. Such enhancements will be far more useful if the
language supports non-open, non-final classes.

There are design techniques that would require additional boilerplate
if we cannot have non-open, non-final classes.

Most importantly, requiring library authors to choose `public` or
`open` provides important documentation value. Users of the library
will know whether the author intends to support subclasses or not.

I think this reasoning is flawed.

If you make any methods overridable outside your module (“open”),
obviously you mean to allow subclassing outside the module. If you have
no open methods, there's absolutely nothing you need to do to “support
subclasses,” and from a design point-of-view, there's no reason to
restrict people from subclassing.

Superclasses can have superclasses, which can themselves have open methods.
This is, in fact, quite common for Cocoa programmers.

John.

···

On Jul 21, 2016, at 10:47 AM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Thu Jul 21 2016, Matthew Johnson <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The only reasons I can see for allowing people to prevent non-final
classes from being subclassed outside the module in which they are
defined are:

1. It feels like a nice point of control to have.

2. Marginal performance gains as noted in the proposal

I personally don't find these to be convincing. #1 in particular seems
like a poor way to make language design decisions. If we decide to add
this point of control, I'll justify it to myself in terms of #2.

P.S., I can live with either alternative; it's just important to me that
we understand the situation clearly when evaluating them.

HTH,

--
Dave

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

Hi Karl,

Please respond to proposal on this thread with your evaluation of it. This isn’t the right place to make counterproposals.

-Chris

···

On Jul 21, 2016, at 8:56 AM, Karl <razielim@gmail.com> wrote:

Just posted in the Review #2 thread. I read the updated proposal, and I have another idea besides making “final” default:

  * What is your evaluation of the proposal?

+1 to the first design. I think this is a great solution that
balances the many considerations that have been raised on all sides of
this issue. `open` is 2 characters shorter than `public` so
complaints about boilerplate are no longer valid. `internal` is the
“default” - neither `public` nor `open` are privileged as a “default”
for publishing API outside of a module.

I am interested in language enhancements such as exhaustive pattern
matching on classes and protocols which rely on knowledge of the full
class hierarchy. Such enhancements will be far more useful if the
language supports non-open, non-final classes.

There are design techniques that would require additional boilerplate
if we cannot have non-open, non-final classes.

Most importantly, requiring library authors to choose `public` or
`open` provides important documentation value. Users of the library
will know whether the author intends to support subclasses or not.

I think this reasoning is flawed.

If you make any methods overridable outside your module (“open”),
obviously you mean to allow subclassing outside the module. If you have
no open methods, there's absolutely nothing you need to do to “support
subclasses,” and from a design point-of-view, there's no reason to
restrict people from subclassing.

Superclasses can have superclasses, which can themselves have open methods.
This is, in fact, quite common for Cocoa programmers.

Okay, good point.

Making a class non-subclassable seems like a pretty indirect way to say
“not even inherited methods should be overridden outside the defining
module,” though.

Wouldn't we prefer to have a way to hide the inheritance relationship
(and thus prevent overriding of inherited methods) outside the module?
Or are these independently useful axes?

···

on Thu Jul 21 2016, John McCall <swift-evolution@swift.org> wrote:

On Jul 21, 2016, at 10:47 AM, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org> wrote:
on Thu Jul 21 2016, Matthew Johnson >> <swift-evolution@swift.org >> <mailto:swift-evolution@swift.org>> > >> wrote:

John.

The only reasons I can see for allowing people to prevent non-final
classes from being subclassed outside the module in which they are
defined are:

1. It feels like a nice point of control to have.

2. Marginal performance gains as noted in the proposal

I personally don't find these to be convincing. #1 in particular seems
like a poor way to make language design decisions. If we decide to add
this point of control, I'll justify it to myself in terms of #2.

P.S., I can live with either alternative; it's just important to me that
we understand the situation clearly when evaluating them.

HTH,

--
Dave

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

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

--
Dave

  * What is your evaluation of the proposal?

+1 to the first design. I think this is a great solution that
balances the many considerations that have been raised on all sides of
this issue. `open` is 2 characters shorter than `public` so
complaints about boilerplate are no longer valid. `internal` is the
“default” - neither `public` nor `open` are privileged as a “default”
for publishing API outside of a module.

I am interested in language enhancements such as exhaustive pattern
matching on classes and protocols which rely on knowledge of the full
class hierarchy. Such enhancements will be far more useful if the
language supports non-open, non-final classes.

There are design techniques that would require additional boilerplate
if we cannot have non-open, non-final classes.

Most importantly, requiring library authors to choose `public` or
`open` provides important documentation value. Users of the library
will know whether the author intends to support subclasses or not.

I think this reasoning is flawed.

If you make any methods overridable outside your module (“open”),
obviously you mean to allow subclassing outside the module. If you have
no open methods, there's absolutely nothing you need to do to “support
subclasses,” and from a design point-of-view, there's no reason to
restrict people from subclassing.

I disagree. As has been discussed when a class is not open the author does not make a commitment to allow subclasses. The right to make the class final is reserved for the future. Maybe this is the “nice point of control” you refer to and don’t find compelling? I would prefer to have library authors acknowledge that they intend to allow subclasses and make that commitment explicit.

For me it isn’t about control as much as it is about making the API contract explicit and acknowledged. I have wondered about the intent of library authors enough times to find this explicit statement in the language worthwhile.

I also think language features enabled by knowing the whole class hierarchy will provide more value than “compositional subclasses” as long as we gain better support for composition elsewhere in the language.

The only reasons I can see for allowing people to prevent non-final
classes from being subclassed outside the module in which they are
defined are:

1. It feels like a nice point of control to have.

2. Marginal performance gains as noted in the proposal

I personally don't find these to be convincing. #1 in particular seems
like a poor way to make language design decisions. If we decide to add
this point of control, I'll justify it to myself in terms of #2.

P.S., I can live with either alternative; it's just important to me that
we understand the situation clearly when evaluating them.

I agree with this.

···

On Jul 21, 2016, at 12:47 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Thu Jul 21 2016, Matthew Johnson <swift-evolution@swift.org> wrote:

HTH,

--
Dave

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

-1 from me. Same reasons as before, I think:

First proposal:
- Conflation of ‘public’ and ‘open’; it feels like open is a new higher access level, like getting married, or going sudo or something. If this proposal was accepted, ‘open' should substitute ‘public’, and never be alongside it (the same way “public private class” makes no sense)

- The concept of classes/class members which are closed until ‘open’ed is a powerful one for documenting classes and writing locally-reasonable code. It shouldn’t be mixed up with exposure to external modules - we already have a concept to describe that; it’s called access levels and ‘public’. I’ve worked with horrible base-classes before with a mixture of internally overridden and non-overriden members, and I would have absolutely loved some compiler-enforced annotation in that case. It makes sure that people stick to the contracts for a class’ members, and that breaking them is something you need to think twice about.

Second proposal:
- I’m arguing for increased local reasoning for classes, this proposal wants to make more of the permissions implicit. I’m not a fan.
- Extra API can be added with extensions. Extra state is something we should look in to in general. Obj-C had associated objects; maybe we can do better with Swift.

Basically, if you consider that we would need an explicit keyword for the default, non-open state (lets say “sealed”) as we have for “internal” - whether or not it applies inside the module is the only difference between the first proposal and my counter-proposal.
We could make that change later, but I think we should go with the sound logic behind this idea, and go with the expectation of safety and local reasonability, and try to make it as consistent as possible from the start. We could loosen it up to only apply to ‘public’ members if too onerous (although my preference would be shorthands - “open-all” or something).

Karl

···

On 21 Jul 2016, at 20:49, Chris Lattner <clattner@apple.com> wrote:

On Jul 21, 2016, at 8:56 AM, Karl <razielim@gmail.com> wrote:

Just posted in the Review #2 thread. I read the updated proposal, and I have another idea besides making “final” default:

Hi Karl,

Please respond to proposal on this thread with your evaluation of it. This isn’t the right place to make counterproposals.

-Chris

  * What is your evaluation of the proposal?

I gave enthusiastic thumbs up to the previous two proposals. I agree
wholeheartedly with the underlying goal, and I love the new Motivation
section. I'm OK with the idea of making "open" imply "public" by
default, which seems to be the gist of the new design.

However, reading the actual details in this third revision made me
uncomfortable. I expected the specification to be bulletproof by now;
but the actual text of the "Proposed design" section seems to be even
less coherent than the previous two.

I understand there's a (self-imposed) deadline, but this one could've
really used a little more time in the oven.

While I am still strongly in favor of the direction and high-level
design of the proposal, I cannot in good conscience argue in favor
of such ill-defined details in a _third_review_. So I'm giving
this specific revision -1 in protest. :cry:

I'm sorry if my drafting wasn't up to snuff. In my defense, I needed to get it out quickly,
and I had a lot to rewrite. I was pretty tired by the time I actually finished the motivation
section.

Here are some of my problems with the text:

1. The proposal offers two separate design approaches for `open` classes, listing
arguments for both. Please excuse my bluntness, but this is now the third attempt
to push this through: With all due respect, would you please make up your mind?

Most of the proposal has been "pushed through", frankly. We are looking for feedback
on this specific question. I agree that the proposal framework is an awkward fit for
this kind of discussion prompt.

2. The option to get rid of sealed classes is brand new with SE-0117rev3.
Did someone argue for it in review #2?

(I'm all for adding better support for sealed class hierarchies, so I prefer plan A.)

It's nice that we're given the option of reducing language complexity, but then
it's bizarre that the idea of eliminating sealed _members_ isn't mentioned at all.

Because it was settled in the core-team review.

Although, a literal reading of "plan B" does in fact seem to imply that any member
that is not explicitly marked open would not even be _internally_ overridable:

    "The second design says that there is no such thing as an open class because
    all classes are subclassable unless made final. Note that its direct methods
    would still not be overridable unless individually made open, although its
    inherited open methods could still be overridden."

There is no mention of "public", and no mention of module boundaries.
Thus, plan B basically proposes to make *all members* in *all classes* `final` by default.
This seems inconsistent with the rest of the proposal, so I'll assume this is a
mistake in wording, not the actual design intent. (Or is it?)

It is a mistake in wording.

Let's assume the intent was to keep members internally overridable by default.
But then why not go the full way? We can achieve the proposal's goals by just tweaking
existing defaults -- there's no need to introduce complicated new overridability levels:

"This proposal does not change the rules of class subclassibility. However, it introduces the
(previously implicit) "open" qualifier to explicitly declare an overridable member,
and changes the rules of default overridability as follows:
   - Members of public classes are now final by default.
   - Members of internal and private classes remain overridable ("open") by default."

I prefer plan A, but I'd also be OK with this bare-bones proposal.

I'm strongly opposed to plan B (either as stated in the proposal or what (I assume
above) is the intent behind it.)

Thank you, that's what we're looking for.

3. The code examples are needlessly complicated by trying to describe both sub-proposals.
I find this is confusing and it obscures the actual effect of both variants.

I'm sorry, I didn't have much of a choice about this. Perhaps I could have broken it out
into two completely different code examples.

The examples are also inconsistent with the text: e.g., AFAICT, property `size` below
is still intended to be overridable inside the module that defines
`SubclassableParentClass`.

     open class SubclassableParentClass {
       // This property is not overridable.
       public var size : Int
       ...
     }

Yes, this should read "not overridable outside of the current module".

4. The previous revisions ignored dynamic members, which wasn't great. The current
document acknowleges that "dynamic" members exists, but then *prohibits*
them from being overridden across a module boundary!

    "`open` is not permitted on declarations that are explicitly `final` or `dynamic`."
    "A class member that is not `open` cannot be overridden outside of the current module."

If this was intentional, I'd love to see the reasoning behind this decision.
Otherwise the wording should be fixed:

    "A class member that is not `open` or `dynamic` cannot be overridden outside
    of the current module."

Yes, sorry, this was a drafting error again.

5. It seems strangely inconsistent that `open` now implies `public` by default,
but `final` doesn't. The proposal fails to explain this inconsistency.
Was this point considered and dismissed, or was it not considered at all?

I think the best model of thinking about "open" is that it is an access level above "public".
The idea of allowing "internal open" came out of discussion but in retrospect does not
seem to hold its own.

Changing `final` to imply `public` later would be a source-breaking change.
Given that SE-0117 is one of the last proposals where maintaining source
compatibility isn't a major consideration, it's unclear if we'll ever have an
opportunity to fix such minor inconsistencies.

The same argument also applies to `dynamic`. If it's OK for `open func` to
imply public visibility, wouldn't it follow from same logic that we should
treat `final func` and `dynamic func` the same way?

6. The proposal does not suggest an explicit spelling for the new default level
of internal-only overridability for public classes and members.

We can add an optional contextual keyword later, in an additive proposal.
However, failing to mention this point makes me question if it was intentionally
omitted to prevent bikeshedding, or just forgotten.

7. "`open` is permitted only on class declarations and overridable class members
(i.e. var, func, and subscript)."

Presumably this list is to be taken to include `class func`, but not `static func`.
Or would `class func` remain open by default?

"class" members are still members. The model for "static" on class members has always
been that they are implicitly final.

8. The opening clause in the "open class members" section makes no sense to me.

    "A class member that overrides an open class member must be explicitly declared open
    unless it is explicitly final or it is a member of a final or non-public class.
    In any case, it is considered open."

So, AFAICU, when a public member of a public open class overrides a superclass member,
we will have to write either "open override" or "final override". Temporary restriction, fine.
(Shouldn't "dynamic override" be a thing, though?)

But if these are "considered open in any case", why force us to add misleading boilerplate?
What does it mean for a member of a final class to be "considered open"?

Today, you would have to write "public override" on such a member. It is not added
boilerplate to say that you have to instead write "open override" unless your intent is to
close off overriding (in which case you have to write "public final override").

9. I have trouble interpreting the parenthesized statement in the following clause:

    "`open` is not permitted on declarations that are explicitly `final` or `dynamic`.
    (Note that it's okay if one or both of the modifiers are implicitly inferred.)"

Can "dynamic" ever be implicitly inferred?

It's inherited. I can't remember if we require it to be explicit on the override.

Can both "dynamic" and "final" ever apply at once?

This is currently forbidden when explicit, but you can declare a dynamic member of a final class.

I assume "it's okay" here means `open` is allowed but ignored, in either case.
Is that right?

Probably the right rule is that open can be subsumed by dynamic, so that you can override
an open member and make it dynamic. I'm not sure whether you should be able to say just
"open" on an override of a dynamic member when it clearly remains dynamic.

Inherited open-ness will be ignored on a final class or member.

Honestly, this is why I don't like to include this level of detail in proposals. People complain if
it's not there, but including it just invites a bunch of complaints about every detail and distracts
from the important parts of the discussion.

John.

···

On Jul 22, 2016, at 9:09 AM, Károly Lőrentey via swift-evolution <swift-evolution@swift.org> wrote:
On 2016-07-21 15:33:37 +0000, Chris Lattner via swift-evolution said:

Ayayay, so much drama!

  * Is the problem being addressed significant enough to warrant a change to Swift?

Yes.

  * Does this proposal fit well with the feel and direction of Swift?

Yes.

  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

See previous reviews.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Probably more than it deserves? ;-)

--
Károly
@lorentey

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

Thank you very much for clarifying these! My position is now of course back in favor of the proposal. +1

(Sorry for being a stickler; I know what deadlines are like. I hope the team gets a bit of time to rest.)

<3,

···

--
Károly
@lorentey

On 2016-07-22, at 18:36, John McCall <rjmccall@apple.com> wrote:

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

On 2016-07-21 15:33:37 +0000, Chris Lattner via swift-evolution said:

  * What is your evaluation of the proposal?

I gave enthusiastic thumbs up to the previous two proposals. I agree
wholeheartedly with the underlying goal, and I love the new Motivation
section. I'm OK with the idea of making "open" imply "public" by
default, which seems to be the gist of the new design.

However, reading the actual details in this third revision made me
uncomfortable. I expected the specification to be bulletproof by now;
but the actual text of the "Proposed design" section seems to be even
less coherent than the previous two.

I understand there's a (self-imposed) deadline, but this one could've
really used a little more time in the oven.

While I am still strongly in favor of the direction and high-level
design of the proposal, I cannot in good conscience argue in favor
of such ill-defined details in a _third_review_. So I'm giving
this specific revision -1 in protest. :cry:

I'm sorry if my drafting wasn't up to snuff. In my defense, I needed to get it out quickly,
and I had a lot to rewrite. I was pretty tired by the time I actually finished the motivation
section.

Here are some of my problems with the text:

1. The proposal offers two separate design approaches for `open` classes, listing
arguments for both. Please excuse my bluntness, but this is now the third attempt
to push this through: With all due respect, would you please make up your mind?

Most of the proposal has been "pushed through", frankly. We are looking for feedback
on this specific question. I agree that the proposal framework is an awkward fit for
this kind of discussion prompt.

2. The option to get rid of sealed classes is brand new with SE-0117rev3.
Did someone argue for it in review #2?

(I'm all for adding better support for sealed class hierarchies, so I prefer plan A.)

It's nice that we're given the option of reducing language complexity, but then
it's bizarre that the idea of eliminating sealed _members_ isn't mentioned at all.

Because it was settled in the core-team review.

Although, a literal reading of "plan B" does in fact seem to imply that any member
that is not explicitly marked open would not even be _internally_ overridable:

   "The second design says that there is no such thing as an open class because
   all classes are subclassable unless made final. Note that its direct methods
   would still not be overridable unless individually made open, although its
   inherited open methods could still be overridden."

There is no mention of "public", and no mention of module boundaries.
Thus, plan B basically proposes to make *all members* in *all classes* `final` by default.
This seems inconsistent with the rest of the proposal, so I'll assume this is a
mistake in wording, not the actual design intent. (Or is it?)

It is a mistake in wording.

Let's assume the intent was to keep members internally overridable by default.
But then why not go the full way? We can achieve the proposal's goals by just tweaking
existing defaults -- there's no need to introduce complicated new overridability levels:

"This proposal does not change the rules of class subclassibility. However, it introduces the
(previously implicit) "open" qualifier to explicitly declare an overridable member,
and changes the rules of default overridability as follows:
  - Members of public classes are now final by default.
  - Members of internal and private classes remain overridable ("open") by default."

I prefer plan A, but I'd also be OK with this bare-bones proposal.

I'm strongly opposed to plan B (either as stated in the proposal or what (I assume
above) is the intent behind it.)

Thank you, that's what we're looking for.

3. The code examples are needlessly complicated by trying to describe both sub-proposals.
I find this is confusing and it obscures the actual effect of both variants.

I'm sorry, I didn't have much of a choice about this. Perhaps I could have broken it out
into two completely different code examples.

The examples are also inconsistent with the text: e.g., AFAICT, property `size` below
is still intended to be overridable inside the module that defines
`SubclassableParentClass`.

    open class SubclassableParentClass {
      // This property is not overridable.
      public var size : Int
      ...
    }

Yes, this should read "not overridable outside of the current module".

4. The previous revisions ignored dynamic members, which wasn't great. The current
document acknowleges that "dynamic" members exists, but then *prohibits*
them from being overridden across a module boundary!

   "`open` is not permitted on declarations that are explicitly `final` or `dynamic`."
   "A class member that is not `open` cannot be overridden outside of the current module."

If this was intentional, I'd love to see the reasoning behind this decision.
Otherwise the wording should be fixed:

   "A class member that is not `open` or `dynamic` cannot be overridden outside
   of the current module."

Yes, sorry, this was a drafting error again.

5. It seems strangely inconsistent that `open` now implies `public` by default,
but `final` doesn't. The proposal fails to explain this inconsistency.
Was this point considered and dismissed, or was it not considered at all?

I think the best model of thinking about "open" is that it is an access level above "public".
The idea of allowing "internal open" came out of discussion but in retrospect does not
seem to hold its own.

Changing `final` to imply `public` later would be a source-breaking change.
Given that SE-0117 is one of the last proposals where maintaining source
compatibility isn't a major consideration, it's unclear if we'll ever have an
opportunity to fix such minor inconsistencies.

The same argument also applies to `dynamic`. If it's OK for `open func` to
imply public visibility, wouldn't it follow from same logic that we should
treat `final func` and `dynamic func` the same way?

6. The proposal does not suggest an explicit spelling for the new default level
of internal-only overridability for public classes and members.

We can add an optional contextual keyword later, in an additive proposal.
However, failing to mention this point makes me question if it was intentionally
omitted to prevent bikeshedding, or just forgotten.

7. "`open` is permitted only on class declarations and overridable class members
(i.e. var, func, and subscript)."

Presumably this list is to be taken to include `class func`, but not `static func`.
Or would `class func` remain open by default?

"class" members are still members. The model for "static" on class members has always
been that they are implicitly final.

8. The opening clause in the "open class members" section makes no sense to me.

   "A class member that overrides an open class member must be explicitly declared open
   unless it is explicitly final or it is a member of a final or non-public class.
   In any case, it is considered open."

So, AFAICU, when a public member of a public open class overrides a superclass member,
we will have to write either "open override" or "final override". Temporary restriction, fine.
(Shouldn't "dynamic override" be a thing, though?)

But if these are "considered open in any case", why force us to add misleading boilerplate?
What does it mean for a member of a final class to be "considered open"?

Today, you would have to write "public override" on such a member. It is not added
boilerplate to say that you have to instead write "open override" unless your intent is to
close off overriding (in which case you have to write "public final override").

9. I have trouble interpreting the parenthesized statement in the following clause:

   "`open` is not permitted on declarations that are explicitly `final` or `dynamic`.
   (Note that it's okay if one or both of the modifiers are implicitly inferred.)"

Can "dynamic" ever be implicitly inferred?

It's inherited. I can't remember if we require it to be explicit on the override.

Can both "dynamic" and "final" ever apply at once?

This is currently forbidden when explicit, but you can declare a dynamic member of a final class.

I assume "it's okay" here means `open` is allowed but ignored, in either case.
Is that right?

Probably the right rule is that open can be subsumed by dynamic, so that you can override
an open member and make it dynamic. I'm not sure whether you should be able to say just
"open" on an override of a dynamic member when it clearly remains dynamic.

Inherited open-ness will be ignored on a final class or member.

Honestly, this is why I don't like to include this level of detail in proposals. People complain if
it's not there, but including it just invites a bunch of complaints about every detail and distracts
from the important parts of the discussion.

John.

Ayayay, so much drama!

  * Is the problem being addressed significant enough to warrant a change to Swift?

Yes.

  * Does this proposal fit well with the feel and direction of Swift?

Yes.

  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

See previous reviews.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Probably more than it deserves? ;-)

--
Károly
@lorentey

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

  * What is your evaluation of the proposal?

Of the designs offered, I prefer #1, because:

1. I'm not convinced that there's actually any sense in subclassing a class with no open members; it seems to me that any class intended to be used like this ought to be redesigned to use composition instead of subclassing.

2. I also think that you're still effectively depending on implementation details about the instance's lifecycle. Suppose I write a database library which has a Record class. It supports uniquing—that is, a single database record is never represented by two different Record instances. If the last external reference to a Record disappears, does the library hold onto that Record and return it again in response to another query, or does it deallocate it and create a new one? That barely matters *unless* you've subclassed Record.

3. Even if there are no overrides, the class and the library around it probably still need to be designed for subclassing. Take the Record example from the previous point: the library will never use your subclass unless there's some hook for telling it which subclass to use. Subclassing isn't going to actually work right if the library doesn't expect any subclasses.

4. Even leaving that aside, you may still foisting surprising memory characteristics on the library. For instance, you might hang a large object graph off an instance that's intended to be lightweight, or create a retain cycle the library author took great pains to avoid.

5. Finally, this doesn't help with the stated goal of allowing you to make a class `final` in a later version of the library.

However, I'm actually strongly in favor of Garth Snyder's call for `open` to explicitly become an access level. In design #1, it almost is already; making it official would simplify many aspects of this design.

(If we do take that road, I would further suggest requiring protocols to be marked `open` instead of `public`. I can easily imagine having closed `public` protocols which are visible but can't be conformed to; this would help with certain typing problems, such as `CKRecordValue`. On the other hand, if `public` were conformable from outside, I can't think of a use for marking a protocol `open`.

I am very much interested in introducing closed protocols. This seems like an interesting and reasonable way to approach it. I like that it makes the choice explicit across module boundaries with no real “default” to speak of - there is no additional “burden” (boilerplate) placed on either decision.

···

On Jul 25, 2016, at 6:38 AM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

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

Protocol extension members should continue to be marked `public`. I could imagine `open` being added later, meaning that the member should be added as a protocol requirement so a specialized implementation can be provided; this would avoid the current boilerplate for defaulted protocol members.)

  * Is the problem being addressed significant enough to warrant a change to Swift?

Yes.

  * Does this proposal fit well with the feel and direction of Swift?

Yes.

  * If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

N/A.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

I put a little bit less effort into this review than I did into the previous two reviews, the many discussion threads on this topic, or several months of thinking this over and changing my mind about the idea as the plan evolved.

--
Brent Royal-Gordon
Architechies

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

-1 to add this proposal for Swift 3.0

I’ve been reading mailing list for the last two reviews of this proposal and discussion turned from “We shouldn’t add this at all” - which was kind of justified to “How actually will it interops with other swift features” - which still has a lot of questions of how exactly this modifiers will play out with other accessibility modifiers, and it seems like there is still no single answer. As Scott said, I have the same feeling that this proposal is being rushed to be accepted before the changed to Swift 3 are locked, and this won’t do any good to its semantics.

Perhaps, core team can write out their thoughts on how this addition to language will fit with all the current class modifiers (final, dynamic etc)? I think it might be accepted during the Swift 3.x phase, when all the edge cases will be thought out, made non-default and then enabled to its full force in Swift 4.0?

  * What is your evaluation of the proposal?

+1 to the first design. I think this is a great solution that
balances the many considerations that have been raised on all sides of
this issue. `open` is 2 characters shorter than `public` so
complaints about boilerplate are no longer valid. `internal` is the
“default” - neither `public` nor `open` are privileged as a “default”
for publishing API outside of a module.

I am interested in language enhancements such as exhaustive pattern
matching on classes and protocols which rely on knowledge of the full
class hierarchy. Such enhancements will be far more useful if the
language supports non-open, non-final classes.

There are design techniques that would require additional boilerplate
if we cannot have non-open, non-final classes.

Most importantly, requiring library authors to choose `public` or
`open` provides important documentation value. Users of the library
will know whether the author intends to support subclasses or not.

I think this reasoning is flawed.

If you make any methods overridable outside your module (“open”),
obviously you mean to allow subclassing outside the module. If you have
no open methods, there's absolutely nothing you need to do to “support
subclasses,” and from a design point-of-view, there's no reason to
restrict people from subclassing.

I disagree. As has been discussed when a class is not open the author
does not make a commitment to allow subclasses.

Yes, but that argument is based on the *assumption* that disallowing
subclassing is somehow an important dimension of safety or preserving
API contracts, independent of overriding. It isn't.

Demonstration: we don't make that argument about aggregation: you can't
prevent someone from making your public struct a stored property in a
type defined outside the module. Judgements about programming style
aside, in the absence of overriding, subclassing and aggregation are
identical from an encapsulation point-of-view.

The right to make the class final is reserved for the future. Maybe
this is the “nice point of control” you refer to and don’t find
compelling? I would prefer to have library authors acknowledge that
they intend to allow subclasses and make that commitment explicit.

The question is, why?

For me it isn’t about control as much as it is about making the API
contract explicit and acknowledged. I have wondered about the intent
of library authors enough times to find this explicit statement in the
language worthwhile.

Yeah, but we don't add language features to represent every possible
author intention, and there's a good argument that any intention that
can be violated without harm shouldn't be enforced.

I also think language features enabled by knowing the whole class
hierarchy will provide more value than “compositional subclasses” as
long as we gain better support for composition elsewhere in the
language.

I agree that there are better ways to solve the composition/API
forwarding problem.

···

on Thu Jul 21 2016, Matthew Johnson <swift-evolution@swift.org> wrote:

On Jul 21, 2016, at 12:47 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Thu Jul 21 2016, Matthew Johnson <swift-evolution@swift.org> wrote:

The only reasons I can see for allowing people to prevent non-final
classes from being subclassed outside the module in which they are
defined are:

1. It feels like a nice point of control to have.

2. Marginal performance gains as noted in the proposal

I personally don't find these to be convincing. #1 in particular seems
like a poor way to make language design decisions. If we decide to add
this point of control, I'll justify it to myself in terms of #2.

P.S., I can live with either alternative; it's just important to me that
we understand the situation clearly when evaluating them.

I agree with this.

HTH,

--
Dave

_______________________________________________
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

--
Dave

  * What is your evaluation of the proposal?

+1 to the first design. I think this is a great solution that
balances the many considerations that have been raised on all sides of
this issue. `open` is 2 characters shorter than `public` so
complaints about boilerplate are no longer valid. `internal` is the
“default” - neither `public` nor `open` are privileged as a “default”
for publishing API outside of a module.

I am interested in language enhancements such as exhaustive pattern
matching on classes and protocols which rely on knowledge of the full
class hierarchy. Such enhancements will be far more useful if the
language supports non-open, non-final classes.

There are design techniques that would require additional boilerplate
if we cannot have non-open, non-final classes.

Most importantly, requiring library authors to choose `public` or
`open` provides important documentation value. Users of the library
will know whether the author intends to support subclasses or not.

I think this reasoning is flawed.

If you make any methods overridable outside your module (“open”),
obviously you mean to allow subclassing outside the module. If you have
no open methods, there's absolutely nothing you need to do to “support
subclasses,” and from a design point-of-view, there's no reason to
restrict people from subclassing.

Superclasses can have superclasses, which can themselves have open methods.
This is, in fact, quite common for Cocoa programmers.

Okay, good point.

Making a class non-subclassable seems like a pretty indirect way to say
“not even inherited methods should be overridden outside the defining
module,” though.

Wouldn't we prefer to have a way to hide the inheritance relationship
(and thus prevent overriding of inherited methods) outside the module?
Or are these independently useful axes?

I agree that it would make sense to be able to say "I allow subclasses, but they
don't get to override any of my methods unless I say so, even things I inherit".
But that feels like a refinement.

John.

···

On Jul 21, 2016, at 1:04 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Thu Jul 21 2016, John McCall <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jul 21, 2016, at 10:47 AM, Dave Abrahams via swift-evolution >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
on Thu Jul 21 2016, Matthew Johnson >>> <swift-evolution@swift.org <mailto:swift-evolution@swift.org> >>> <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>> >> >>> wrote:

John.

The only reasons I can see for allowing people to prevent non-final
classes from being subclassed outside the module in which they are
defined are:

1. It feels like a nice point of control to have.

2. Marginal performance gains as noted in the proposal

I personally don't find these to be convincing. #1 in particular seems
like a poor way to make language design decisions. If we decide to add
this point of control, I'll justify it to myself in terms of #2.

P.S., I can live with either alternative; it's just important to me that
we understand the situation clearly when evaluating them.

HTH,

--
Dave

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

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

--
Dave

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

+1

[ Long, sorry… ]

This version is a big step forward! Thanks for the continued work and comments...

I want to propose a small reframing that I think would help to clarify some of the remaining issues. It’s not really a “counterproposal” because I don’t think it actually changes all that much about the proposal. It’s more a question of how one conceptualizes the changes and fits them into the existing framework.

The gist is: let’s let the second shoe drop and admit that in the current proposal, “open” is now an access level modifier, pure and simple.

In the original proposal (and the ensuing discussion), there was tacit agreement that subclassability/overridability and access levels should be orthogonal. However, given the direction that the design has taken since then, I think we should revisit that decision.

open IS in fact an access level. I can’t say it any better than the proposal itself: “Since the first release of Swift, marking a class public has provided two capabilities: it allows other modules to instantiate and use the class, and it also allows other modules to define subclasses of it. Similarly, marking a class member (a method, property, or subscript) public has provided two capabilities: it allows other modules to use the member, and it also allows those modules to override it…This proposal suggests distinguishing these concepts. A public member will only be usable by other modules, but not overridable. An open member will be both usable and overridable. Similarly, a public class will only be usable by other modules, but not subclassable. An open class will be both usable and subclassable.”

In other words, subclassability/overridability always was an access level issue. All we are doing now is subdividing public into two separate sublevels, public and open. Just as public subsumes all the privileges of internal, open subsumes public.

Arguments:

First, the vast majority of resistance to this proposal (including my own, originally) has centered on the sense that coding options are being removed for potentially or partially ideological reasons (see SoftwareDevelopmentAttitude <http://martinfowler.com/bliki/SoftwareDevelopmentAttitude.html&gt;\), without clear value being offered in return. Reframing open as an access level completely nullifies this objection. “internal” is already the default access level, and the community seems very comfortable with this. For public APIs, developers are now simply required to make a neutral choice between public and open. There’s no strong-arming and no surprising imposition of new restrictions. Developers just have to make exactly the same, explicit access level decisions they did before. (For public API, the default is already so restrictive as to be moot. No accusations of “you picked the wrong default”!)

Second, framing open as an access level automatically resolves the ambiguity between proposal options #1 and #2, in favor of #1 (classes can be marked open). The reason there’s ambiguity about this choice is that there’s ambiguity about what open "really is.” Pin down exactly how open fits into the larger language, and the resolution is obvious. We already know what it means for a class and its members to have different access levels: the members are clamped to the access level of the container. All of the arguments that led to this convention — chiefly, that one may want to keep eventual publication in mind while developing and then be able to “flip the switch” in one place — apply equally to the additional privileges of open.

Third, developers already understand access levels and how they interact. If open is just an access level, all of this proposal’s changes can be fully and naturally described in one line: “public no longer includes the right to subclass or override. To get the behavior formerly known as public, use open instead.” Clear, concise, and not very controversial.

Fourth, bending over backwards to insist that open is not an access level leads to a variety of weird effects and special cases. For example, the fact that open implies public unless otherwise stated, which is mighty strange for modifiers that are supposedly orthogonal. Not to mention all the potential headbutts mentioned earlier by Xiaodi Wu; I agree that open in combination with internal seems oxymoronic. All of these nits would just go away if open were an access level. Again, all of this is the case because open really does quack like an access level and walk like an access level.

Points:

Q: “But what about access-leveled entities for which ‘open’ doesn’t make sense? What does ‘open struct’ mean?”

···

A: It doesn’t mean anything and should be an error. Simple. It’s not as if there weren’t all kinds of above-grammar-level restrictions in the current design…

Q: “What about conflicts with other modifiers, e.g. ‘open is not permitted on declarations that are explicitly final or dynamic’? Isn’t it weird that a simple access level could cause this kind of conflict?”
A: Au contraire, it’s final and dynamic that impose special requirements. You can just as easily flip this around: “final may not be applied to objects at the open access level”. Doesn’t that make more sense anyway?

Q: “You yourself (Garth) have argued that there’s no value to being able to forbid subclassing at the class level, and others have taken this position as well. So why are you now so eager to add ‘open’ to class definitions?”
A: Because it leads to a simple, consistent, and uncontroversial design. I do think there is value in being able to “flip the switch” at the class level as well. From the technical/compiler perspective, it seems like most benefits derive from method-level restrictions (as the current proposal seems to suggest). However, from the perspective of API clients, the top-level question is always going to be “should I be subclassing this or not?” I wouldn’t argue in favor of a class-level keyword just for the purpose of documenting intention, but since we get it for free with these other benefits, I’m all for it.

Garth

Matthew, I also thought that way but I think there is no harm in
allowing one to create a subclass if there are no open methods in it,
meaning there is no way a subclass can even replace an existing
implementation. Even if Swift were to allow creating a new method with
the same name of an existing superclass method the new method should
not respond for calls made using the superclass as base. This is
allowed in C# where you can have the following (*dusts off*):

   class A {
      public virtual void method() {
          Console.println("method in A");
      }
   }

   class B : A {
       public final void method() {
          Console.println("method in B");
      }
   }

   class C : B {
       public new void method() {
          Console.println("method in C");
       }
   }

   B b = new C();
   b.method(); // prints "method in B"
   C c = (C) b;
   c.method(); // prints "method in C"

I know I'm being a little flexible here given my previous messages to
this list but if we are to allow subclasses to be openly created,
subclasses will only be allowed to implement new interfaces and create
new methods but not mess with the base implementation unless a method
is explicitly open, there is no harm to the defined contract, so I see
no harm in this approach for composition.

And yes, contracts are about (at least some) control.

L

···

On 21 July 2016 at 15:08, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Jul 21, 2016, at 12:47 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

on Thu Jul 21 2016, Matthew Johnson <swift-evolution@swift.org> wrote:

    * What is your evaluation of the proposal?

+1 to the first design. I think this is a great solution that
balances the many considerations that have been raised on all sides of
this issue. `open` is 2 characters shorter than `public` so
complaints about boilerplate are no longer valid. `internal` is the
“default” - neither `public` nor `open` are privileged as a “default”
for publishing API outside of a module.

I am interested in language enhancements such as exhaustive pattern
matching on classes and protocols which rely on knowledge of the full
class hierarchy. Such enhancements will be far more useful if the
language supports non-open, non-final classes.

There are design techniques that would require additional boilerplate
if we cannot have non-open, non-final classes.

Most importantly, requiring library authors to choose `public` or
`open` provides important documentation value. Users of the library
will know whether the author intends to support subclasses or not.

I think this reasoning is flawed.

If you make any methods overridable outside your module (“open”),
obviously you mean to allow subclassing outside the module. If you have
no open methods, there's absolutely nothing you need to do to “support
subclasses,” and from a design point-of-view, there's no reason to
restrict people from subclassing.

I disagree. As has been discussed when a class is not open the author does not make a commitment to allow subclasses. The right to make the class final is reserved for the future. Maybe this is the “nice point of control” you refer to and don’t find compelling? I would prefer to have library authors acknowledge that they intend to allow subclasses and make that commitment explicit.

For me it isn’t about control as much as it is about making the API contract explicit and acknowledged. I have wondered about the intent of library authors enough times to find this explicit statement in the language worthwhile.

I also think language features enabled by knowing the whole class hierarchy will provide more value than “compositional subclasses” as long as we gain better support for composition elsewhere in the language.

The only reasons I can see for allowing people to prevent non-final
classes from being subclassed outside the module in which they are
defined are:

1. It feels like a nice point of control to have.

2. Marginal performance gains as noted in the proposal

I personally don't find these to be convincing. #1 in particular seems
like a poor way to make language design decisions. If we decide to add
this point of control, I'll justify it to myself in terms of #2.

P.S., I can live with either alternative; it's just important to me that
we understand the situation clearly when evaluating them.

I agree with this.

HTH,

--
Dave

_______________________________________________
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

Just posted in the Review #2 thread. I read the updated proposal, and I have another idea besides making “final” default:

Hi Karl,

Please respond to proposal on this thread with your evaluation of it. This isn’t the right place to make counterproposals.

-Chris

-1 from me. Same reasons as before, I think:

I’m -1 as well, for the reasons that have already been given, but since it’s clear that this thing’s going to be rammed down our throats no matter how we feel about it, we might as well try to make lemonade:

First proposal:
- Conflation of ‘public’ and ‘open’; it feels like open is a new higher access level, like getting married, or going sudo or something. If this proposal was accepted, ‘open' should substitute ‘public’, and never be alongside it (the same way “public private class” makes no sense)

Actually, if ‘public’ and ‘open’ are separated, it might allow us to finally have some sort of ‘protected’ access level. ‘private open’ implies something that can be subclassed but not otherwise accessed, which would more or less provide ‘protected’ functionality.

Charles

···

On Jul 21, 2016, at 2:29 PM, Karl via swift-evolution <swift-evolution@swift.org> wrote:

On 21 Jul 2016, at 20:49, Chris Lattner <clattner@apple.com <mailto:clattner@apple.com>> wrote:
On Jul 21, 2016, at 8:56 AM, Karl <razielim@gmail.com <mailto:razielim@gmail.com>> wrote:

All of these nits would just go away if open were an access level. Again,
all of this is the case because open *really does* quack like an access
level and walk like an access level.

+1

Well-said Garth.

Nevin

···

On Thu, Jul 21, 2016 at 6:29 PM, Garth Snyder via swift-evolution < swift-evolution@swift.org> wrote:

+1

[ Long, sorry… ]

This version is a big step forward! Thanks for the continued work and
comments...

I want to propose a small *reframing* that I think would help to clarify
some of the remaining issues. It’s not really a “counterproposal” because I
don’t think it actually changes all that much about the proposal. It’s more
a question of how one conceptualizes the changes and fits them into the
existing framework.

*The gist is:* let’s let the second shoe drop and admit that in the
current proposal, “open” is now an access level modifier, pure and simple.

In the original proposal (and the ensuing discussion), there was tacit
agreement that subclassability/overridability and access levels should be
orthogonal. However, given the direction that the design has taken since
then, I think we should revisit that decision.

open *IS* in fact an access level. I can’t say it any better than the
proposal itself: *“Since the first release of Swift, marking a class
public has provided two capabilities: it allows other modules to
instantiate and use the class, and it also allows other modules to define
subclasses of it. Similarly, marking a class member (a method, property, or
subscript) public has provided two capabilities: it allows other modules to
use the member, and it also allows those modules to override it…This
proposal suggests distinguishing these concepts. A public member will only
be usable by other modules, but not overridable. An open member will be
both usable and overridable. Similarly, a public class will only be usable
by other modules, but not subclassable. An open class will be both usable
and subclassable.”*

In other words, subclassability/overridability *always was *an access
level issue. *All we are doing now* is subdividing public into two
separate sublevels, public and open. Just as public subsumes all the
privileges of internal, open subsumes public.

*Arguments:*
*First*, the vast majority of resistance to this proposal (including my
own, originally) has centered on the sense that coding options are being
removed for potentially or partially ideological reasons (see
SoftwareDevelopmentAttitude
<http://martinfowler.com/bliki/SoftwareDevelopmentAttitude.html&gt;\),
without clear value being offered in return. Reframing open as an access
level *completely nullifies *this objection. “internal” is already the
default access level, and the community seems very comfortable with this.
For public APIs, developers are now simply required to make a neutral
choice between public and open. There’s no strong-arming and no surprising
imposition of new restrictions. Developers just have to make exactly the
same, explicit access level decisions they did before. (For public API, the
default is already so restrictive as to be moot. No accusations of “you
picked the wrong default”!)

*Second*, framing open as an access level automatically resolves the
ambiguity between proposal options #1 and #2, in favor of #1 (classes
can be marked open). The reason there’s ambiguity about this choice is that
there’s ambiguity about what open "really is.” Pin down exactly how open
fits into the larger language, and the resolution is obvious. We already
know what it means for a class and its members to have different access
levels: the members are clamped to the access level of the container. All
of the arguments that led to this convention — chiefly, that one may want
to keep eventual publication in mind while developing and then be able to
“flip the switch” in one place — apply equally to the additional
privileges of open.

*Third*, developers already understand access levels and how they
interact. If open is just an access level, all of this proposal’s changes
can be fully and naturally described in one line: “public no longer
includes the right to subclass or override. To get the behavior formerly
known as public, use open instead.” Clear, concise, and not very
controversial.

*Fourth*, bending over backwards to insist that open is not an access
level leads to a variety of weird effects and special cases. For example,
the fact that open implies public unless otherwise stated, which is mighty
strange for modifiers that are supposedly orthogonal. Not to mention all
the potential headbutts mentioned earlier by Xiaodi Wu; I agree that open
in combination with internal seems oxymoronic. All of these nits would just
go away if open were an access level. Again, all of this is the case
because open *really does* quack like an access level and walk like an
access level.

*Points:*
*Q:* “But what about access-leveled entities for which ‘open’ doesn’t
make sense? What does ‘open struct’ mean?”
*A:* It doesn’t mean anything and should be an error. Simple. It’s not as
if there weren’t all kinds of above-grammar-level restrictions in the
current design…

*Q:* “What about conflicts with other modifiers, e.g. ‘open is not
permitted on declarations that are explicitly final or dynamic’? Isn’t it
weird that a simple access level could cause this kind of conflict?”
*A:* Au contraire, it’s final and dynamic that impose special
requirements. You can just as easily flip this around: “final may not be
applied to objects at the open access level”. Doesn’t that make more sense
anyway?

*Q:* “You yourself (Garth) have argued that there’s no value to being
able to forbid subclassing at the class level, and others have taken this
position as well. So why are you now so eager to add ‘open’ to class
definitions?”
*A:* Because it leads to a simple, consistent, and uncontroversial
design. I do think there is value in being able to “flip the switch” at the
class level as well. From the technical/compiler perspective, it seems like
most benefits derive from method-level restrictions (as the current
proposal seems to suggest). However, from the perspective of API clients,
the top-level question is always going to be “should I be subclassing this
or not?” I wouldn’t argue in favor of a class-level keyword just for the
purpose of documenting intention, but since we get it for free with these
other benefits, I’m all for it.

Garth

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

You can't change what you cannot see, so orthogonality was never a real option.
It could be, if "public" did not affect visibility, but rather the right to instantiate/call. In this scenario, you could specify an abstract class "for free" as "private open class Foo".
I'm still convinced that such synergies are highly desirable, and that a holistic take on the whole topic would be more beneficial for Swift than the small-scale changes that have been discussed so far.

···

Am 22.07.2016 um 00:29 schrieb Garth Snyder via swift-evolution <swift-evolution@swift.org>:

In the original proposal (and the ensuing discussion), there was tacit agreement that subclassability/overridability and access levels should be orthogonal. However, given the direction that the design has taken since then, I think we should revisit that decision.

on Thu Jul 21 2016, Matthew Johnson
<swift-evolution@swift.org
<mailto:swift-evolution@swift.org>
<mailto:swift-evolution@swift.org
<mailto:swift-evolution@swift.org>>>

wrote:

  * What is your evaluation of the proposal?

+1 to the first design. I think this is a great solution that
balances the many considerations that have been raised on all sides of
this issue. `open` is 2 characters shorter than `public` so
complaints about boilerplate are no longer valid. `internal` is the
“default” - neither `public` nor `open` are privileged as a “default”
for publishing API outside of a module.

I am interested in language enhancements such as exhaustive pattern
matching on classes and protocols which rely on knowledge of the full
class hierarchy. Such enhancements will be far more useful if the
language supports non-open, non-final classes.

There are design techniques that would require additional boilerplate
if we cannot have non-open, non-final classes.

Most importantly, requiring library authors to choose `public` or
`open` provides important documentation value. Users of the library
will know whether the author intends to support subclasses or not.

I think this reasoning is flawed.

If you make any methods overridable outside your module (“open”),
obviously you mean to allow subclassing outside the module. If you have
no open methods, there's absolutely nothing you need to do to “support
subclasses,” and from a design point-of-view, there's no reason to
restrict people from subclassing.

Superclasses can have superclasses, which can themselves have open methods.
This is, in fact, quite common for Cocoa programmers.

Okay, good point.

Making a class non-subclassable seems like a pretty indirect way to say
“not even inherited methods should be overridden outside the defining
module,” though.

Wouldn't we prefer to have a way to hide the inheritance relationship
(and thus prevent overriding of inherited methods) outside the module?
Or are these independently useful axes?

I agree that it would make sense to be able to say "I allow
subclasses, but they don't get to override any of my methods unless I
say so, even things I inherit". But that feels like a refinement.

A refinement of what?

To me, “I don't allow any overrides outside my module” is a much more
useful thing to be able to say than “I don't allow subclasses.” If we
made “open” on a class mean that it's possible to override methods in
other modules, then it would acheive that purpose, and it would leave
“open” with a consistent meaning related only to overriding.

···

on Thu Jul 21 2016, John McCall <swift-evolution@swift.org> wrote:

On Jul 21, 2016, at 1:04 PM, Dave Abrahams via swift-evolution >> <swift-evolution@swift.org> wrote:
on Thu Jul 21 2016, John McCall >> <swift-evolution@swift.org >> <mailto:swift-evolution@swift.org>> > >> wrote:

On Jul 21, 2016, at 10:47 AM, Dave Abrahams via swift-evolution >>>> <swift-evolution@swift.org >>>> <mailto:swift-evolution@swift.org>> >>>> wrote:

John.

John.

The only reasons I can see for allowing people to prevent non-final
classes from being subclassed outside the module in which they are
defined are:

1. It feels like a nice point of control to have.

2. Marginal performance gains as noted in the proposal

I personally don't find these to be convincing. #1 in particular seems
like a poor way to make language design decisions. If we decide to add
this point of control, I'll justify it to myself in terms of #2.

P.S., I can live with either alternative; it's just important to me that
we understand the situation clearly when evaluating them.

HTH,

--
Dave

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

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

--
Dave

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

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

--
Dave