The last explanation is great, now I could follow the idea behind the proposed 
closed keyword/access modifier. I do now understand the contract on enums, but 
I’m struggling to understand how closed would work in my own codebase. Assume I 
had a closed (not the public meaning of your proposal) protocol.

Am I allowed to conform to that protocol inside my library? If it’s a version 
controlled feature, how to prevent further conformance to that protocol in a 
newer version?

What would an access modifier like closed mean on type members (final public)? 
If it’s only a type scope access modifier, it leans towards being an attribute 
instead.

If it’s not an access modifier, would closed have some versioning paramerters?
Bikeshedding: @closed(1.0.0) (The version, the type was closed)
On current enums: @closed(*) (From the start)
The latter implies in my head that each type needs an explicit version 
annotation like @available(1.0.0, *)!?

How do we handle bug fixes on closed types? Assume all current enums would get 
an annotation like @closed(*) public. After normalize enum case representation 
proposal you might also want to clean up the names of your cases. That means 
you have to perform a breaking API change.
For example in one of my ‘just for fun’ projects I have two enum cases that 
look as follows:

case javaScript(String)
case scopedJavaScript(String, scope: Document)
If the mentioned proposal will be accepted I’d like to make these cases shiny.

// v1:
case javaScript(String)
case javaScript(String, scope: Document)
     
// or v2:
case javaScript(String, scope: Document? = nil)


-- 
Adrian Zubarev
Sent with Airmail

Am 9. Februar 2017 um 16:57:40, Matthew Johnson via swift-evolution 
([email protected]) schrieb:


On Feb 8, 2017, at 5:48 PM, Xiaodi Wu <[email protected]> wrote:

I agree very much with rationalizing access levels, but I'm not sure I like 
this proposal for public vs. closed. How would the compiler stop me from 
editing my own code if something is closed? The answer must be that it can't, 
so I can't see it as a co-equal to open but rather simply a statement of 
intention. Therefore I think use cases for the proposed behavior of closed 
would be better served by annotations and proper semantic versioning.

The most important point IMO is that they *are* co-equal in the sense that they 
define a contract between library authors, library users and the compiler.  As 
you note, there are some differences in how the `closed` contract is supported. 
 But that is far less important than the meaning of the contract itself.

Dave's comment about tools to assist with contract-compatible API evolution is 
the right way to think about this.  Of course you *can* make breaking changes, 
but we want to make it clear when you *are* making a breaking change, both for 
source and for ABI compatibility.  This will help library authors, but it also 
helps users as well as the compiler reason about code when we are able to offer 
stronger guarantees.

Most notably, the behavior of public enums *already* has the API contract of 
`closed` and we do not want to remove that capability.  This proposal only 
formalizes how that contract is specified and makes it consistent across all 
kinds of types.  It *does not* introduce the idea of a closed semantic contract 
for a type.

As this change didn't seem in scope for Swift 4 phase 1, I've held off on 
discussing my own thoughts on access levels. The idea I was going to propose in 
phase 2 was to have simply open and public enums (and protocols). I really 
think that completes access levels in a rational way without introducing 
another keyword.

The reason I posted now is because formalizing this API contract for enums must 
happen before ABI is locked down, and also because there is at least one 
protocol in the standard library (`MirrorPath`) which is documented with the 
intent that it be `closed`.

I understand the reluctance to introduce another keyword.  It isn’t clear to me 
what semantics you assign to `open` and `public` enums.

Are you suggesting that they match the semantics defined in my proposal and 
suggesting closed enums (i.e. matching the current behavior of `public` enums) 
would require an `@closed` annotation as suggested in the Library Evolution 
document?  I am opposed to this approach because it penalizes the API contract 
that I think is often the most appropriate for enums.  I strongly prefer that 
we adopt the same neutral stance that we when we introduced `open`.  

On the other hand, you might be suggesting that `public` enums maintain their 
current behavior and we simply introduce `open` as a modifier that reserves the 
right for the *library* to introduce new cases while continuing to prohibit 
*users* from introducing new cases.  This approach has inconsistent semantics 
for both `public` and `open`.  These keywords would indicate a different API 
contract for enums than they do for classes and protocols.  In fact, `open` for 
enums would have a contract analagous with `public` for classes and protocols.  
This feels like a recipe for confusion.  IMO, having consistent semantics for 
each keyword is pretty important.  We already have, and desire to continue to 
have, three distinct semantic contracts.  If we want keywords with consistent 
semantics we are going to have to introduce a new keyword for the third meaning.


On Wed, Feb 8, 2017 at 17: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