Hello Jordan, having a clear and constructive opinion like yours is much 
appreciated in this topic.

As a disclaimer, I haven’t followed the original topic about the new open 
access modifier that much last year. At day one, when Swift 3 was released, I 
noticed open everywhere in Apples libraries like UIKit. I quickly had a short 
glance on how it supposed to work, because I literally run into the issue where 
the open is prohibited on initializers, which felt odd. Then I had a closer 
look to the proposal and realized it’s by design; but it still feels strange, 
because public-but-not-open claims to disallow overriding a type member from 
module A in module B, where open removes that restriction. Later on thinking 
how neat the differentiation between public and open was I thought, because 
it’s an ACCESS MODIFIER, it should work with any type we had in swift. Okay it 
doesn’t work with value types because they don’t have any sub-typing 
relationship which the client of my module could create - that’s totally fine 
by me. At the end of that journey I remembered that I had some protocols which 
I had to make public before Swift 3 because it was an implementation detail. 
Also it was really interesting to see that non of my protocols were open by 
default after the migration process. At that point I tried to open these 
protocols where I thought the client should be able to conform to them, because 
public should mean public-but-not-open right? And this is how I hit the wall 
with this.

I felt I had to share my experience on this.

Now that you’ve mentioned an alternative for protocols, such as @closed public. 
IMHO this would be a really shame if this was the final decision for this issue.

Iff we ever decide to bring sub-typing to value types, that would imply that 
we’d end up with open class/struct/enum vs. @closed public protocol, or even 
worse open class vs. @closed public protocols/struct/enum. It is simply saying 
inconsistent.

Having an exclusive access modifier for classes is simply not worth calling 
open an access modifier, because from the definitions it should affect all 
types and their members but not being exclusive to classes, otherwise it should 
have been open public, just like final public is.

Having an exclusive attribute for protocols, which only works with the public 
access modifier is nuts. Furthermore that would not only reserve the @closed 
attribute, but could potentially lead to even more inconsistency if we consider 
to add something like @closed to enums or elsewhere. In the current scenario 
@closed public has simply no other meaning than public on classes would have, 
but public would be open for protocols. o.O It definitely will become an origin 
of confusion at that point.

I understand that this is somehow a breaking change, but currently open feels 
simply as a rushed decision from last year. In its current state I think it’s a 
regression.

P.S.: Please don’t blame me for my horrible English. ;)



-- 
Adrian Zubarev
Sent with Airmail

Am 10. Februar 2017 um 19:52:23, Jordan Rose via swift-evolution 
([email protected]) schrieb:

Hi, Matthew. Thank you for bringing up these issues. I'm going to break my 
feedback up into separate messages, because I think really the enum and 
protocol cases are unrelated. Open classes refer to classes that can be 
subclassed from clients of the current module, and similarly open protocols 
would be protocols that can be adopted from clients of the current module. 
Public-but-not-open classes cannot be subclassed from outside the current 
module, but they can still be subclassed within the module. By contrast, "open" 
enums can grow new cases in new versions of the library, but clients still 
can't add cases. (That's not a totally unreasonable feature to ever consider, 
but it's not the one we need now.)

This message will talk about protocols (and a bit about classes); I'll put my 
thoughts on enums in another message.

I'm with you (and Adrian) in thinking "public-but-not-open" protocols are 
useful. The Apple frameworks have a good handful of these, for cases where a 
framework doesn't want to tie itself to a particular type, but still wants to 
vend something with certain operations. Public-but-not-open protocols also 
allow for non-public requirements, i.e. operations that every conforming type 
has but only the library needs to call.

However, we still have a hurdle here: is this useful enough to change the 
defaults for it? Another way to implement this is to leave 'public' as is (with 
its open-like behavior), but have an annotation to say that it can only be 
conformed to from within the module. (`@closed public protocol Foo`) This isn't 
necessarily what we would have done from the start, but breaks between Swift 3 
and Swift 4 have a higher bar to clear than between Swift 2 and Swift 3.

It's worth noting that this can all be emulated by a struct with a non-public 
field of protocol type and forwarding operations, but that's a lot of extra 
work today. We want to make the correct thing easy; in the face of writing a 
wrapper type, I suspect many library authors would just give up and make the 
protocol public.

I don't think the proposed "closed" for classes and protocols is interesting. 
"No new subclasses/adopters" seems only marginally useful for optimization, and 
not at all useful at a semantic level. "public-but-not-open" is the interesting 
access level.

That's about all I've got for protocols. Thanks again for bringing it up.
Jordan

P.S. For classes, note that 'final' is essentially a performance optimization 
at this point. I'm not even sure we should bother displaying it in generated 
interfaces (although the compiler should still be able to take advantage of it 
in clients).



On Feb 8, 2017, at 15:05, Matthew Johnson via swift-evolution 
<[email protected]> wrote:

I’ve been thinking a lot about our public access modifier story lately in the 
context of both protocols and enums.  I believe we should move further in the 
direction we took when introducing the `open` keyword.  I have identified what 
I think is a promising direction and am interested in feedback from the 
community.  If community feedback is positive I will flesh this out into a more 
complete proposal draft.


Background and Motivation:

In Swift 3 we had an extended debate regarding whether or not to allow 
inheritance of public classes by default or to require an annotation for 
classes that could be subclassed outside the module.  The decision we reached 
was to avoid having a default at all, and instead make `open` an access 
modifier.  The result is library authors are required to consider the behavior 
they wish for each class.  Both behaviors are equally convenient (neither is 
penalized by requiring an additional boilerplate-y annotation).

A recent thread 
(https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031566.html)
 discussed a similar tradeoff regarding whether public enums should commit to a 
fixed set of cases by default or not.  The current behavior is that they *do* 
commit to a fixed set of cases and there is no option (afaik) to modify that 
behavior.  The Library Evolution document 
(https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums) 
suggests a desire to change this before locking down ABI such that public enums 
*do not* make this commitment by default, and are required to opt-in to this 
behavior using an `@closed` annotation.

In the previous discussion I stated a strong preference that closed enums *not* 
be penalized with an additional annotation.  This is because I feel pretty 
strongly that it is a design smell to: 1) expose cases publicly if consumers of 
the API are not expected to switch on them and 2) require users to handle 
unknown future cases if they are likely to switch over the cases in correct use 
of the API.

The conclusion I came to in that thread is that we should adopt the same 
strategy as we did with classes: there should not be a default.

There have also been several discussions both on the list and via Twitter 
regarding whether or not we should allow closed protocols.  In a recent Twitter 
discussion Joe Groff suggested that we don’t need them because we should use an 
enum when there is a fixed set of conforming types.  There are at least two  
reasons why I still think we *should* add support for closed protocols.

As noted above (and in the previous thread in more detail), if the set of types 
(cases) isn’t intended to be fixed (i.e. the library may add new types in the 
future) an enum is likely not a good choice.  Using a closed protocol 
discourages the user from switching and prevents the user from adding 
conformances that are not desired.

Another use case supported by closed protocols is a design where users are not 
allowed to conform directly to a protocol, but instead are required to conform 
to one of several protocols which refine the closed protocol.  Enums are not a 
substitute for this use case.  The only option is to resort to documentation 
and runtime checks.


Proposal:

This proposal introduces the new access modifier `closed` as well as clarifying 
the meaning of `public` and expanding the use of `open`.  This provides 
consistent capabilities and semantics across enums, classes and protocols.

`open` is the most permissive modifier.  The symbol is visible outside the 
module and both users and future versions of the library are allowed to add new 
cases, subclasses or conformances.  (Note: this proposal does not introduce 
user-extensible `open` enums, but provides the syntax that would be used if 
they are added to the language)

`public` makes the symbol visible without allowing the user to add new cases, 
subclasses or conformances.  The library reserves the right to add new cases, 
subclasses or conformances in a future version.

`closed` is the most restrictive modifier.  The symbol is visible publicly with 
the commitment that future versions of the library are *also* prohibited from 
adding new cases, subclasses or conformances.  Additionally, all cases, 
subclasses or conformances must be visible outside the module.

Note: the `closed` modifier only applies to *direct* subclasses or 
conformances.  A subclass of a `closed` class need not be `closed`, in fact it 
may be `open` if the design of the library requires that.  A class that 
conforms to a `closed` protocol also need not be `closed`.  It may also be 
`open`.  Finally, a protocol that refines a `closed` protocol need not be 
`closed`.  It may also be `open`.

This proposal is consistent with the principle that libraries should opt-in to 
all public API contracts without taking a position on what that contract should 
be.  It does this in a way that offers semantically consistent choices for API 
contract across classes, enums and protocols.  The result is that the language 
allows us to choose the best tool for the job without restricting the designs 
we might consider because some kinds of types are limited with respect to the 
`open`, `public` and `closed` semantics a design might require.


Source compatibility:

This proposal affects both public enums and public protocols.  The current 
behavior of enums is equivalent to a `closed` enum under this proposal and the 
current behavior of protocols is equivalent to an `open` protocol under this 
proposal.  Both changes allow for a simple mechanical migration, but that may 
not be sufficient given the source compatibility promise made for Swift 4.  We 
may need to identify a multi-release strategy for adopting this proposal.

Brent Royal-Gordon suggested such a strategy in a discussion regarding closed 
protocols on Twitter:

* In Swift 4: all unannotated public protocols receive a warning, possibly with 
a fix-it to change the annotation to `open`.
* Also in Swift 4: an annotation is introduced to opt-in to the new `public` 
behavior.  Brent suggested `@closed`, but as this proposal distinguishes 
`public` and `closed` we would need to identify something else.  I will use 
`@annotation` as a placeholder.
* Also In Swift 4: the `closed` modifier is introduced.

* In Swift 5 the warning becomes a compiler error.  `public protocol` is not 
allowed.  Users must use `@annotation public protocol`.
* In Swift 6 `public protocol` is allowed again, now with the new semantics.  
`@annotation public protocol` is also allowed, now with a warning and a fix-it 
to remove the warning.
* In Swift 7 `@annotation public protocol` is no longer allowed.

A similar mult-release strategy would work for migrating public enums.


_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to