> On 9 Feb 2017, at 00:05, Matthew Johnson via swift-evolution 
> <swift-evolution@swift.org> 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
>  
> <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 
> <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
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

A couple of points:

1) Protocols can also be sub-typed as well as conformed to. “Open” for classes 
refers to sub-typing. As I understand it, you would not be able to derive a 
protocol from a non-open protocol. Perhaps worth mentioning.

2) Enums are conceptually closed - that’s the whole point behind requiring 
exhaustive switches. Allowing later library versions to add/remove cases is 
important, but adding default cases to handle case “?” sounds like a bad 
solution. With zero information about what is happening, and the chance that 
some cases may not exist any more, anything you write in such a default 
statement would be useless and nonsensical.

I think API versioning is the better way to deal with this. Version 3 of 
MyLibrary.MyEnum has cases { A, B, C }, and Version 3.1 has cases { A, C, D, E 
}. I should be able to write code which handles either complete set of cases, 
depending on which version of MyLibrary is installed.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to