Sent from my iPad
> On Feb 10, 2017, at 9:48 PM, Xiaodi Wu <[email protected]> wrote:
>
>> On Wed, Feb 8, 2017 at 5:05 PM, 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.
>
> A different line of feedback here:
>
> As per previous reply, I now think if we clarify the mental model of the
> access modifier hierarchy you're proposing and adopt or reject with that
> clarity, we'll be fine whether we go with `closed` or with `@closed`. But I
> don't think the source compatibility strategy you list is the most simple or
> the most easy to understand for end users.
I'm pretty neutral on what kind of source compatibility strategy we would
adopt. I am happy to defer to the community and core team.
>
> - I'll leave aside closed protocols, which as per Jordan Rose's feedback may
> or may not have sufficient interestingness.
Jordan supported allowing protocols to have the same choice of contract that
classes do today. `public protocol` has the same meaning as `open class` today
so if we want consistency we need a breaking change.
> - With respect to enums, I don't think we need such a drastic whiplash in
> terms of what will compile in future versions. Instead, we could take a more
> pragmatic approach:
>
> 1. In Swift 4, remove the warning (or is it error?) about `default` cases in
> switch statements over public enums. Simultaneously, add `closed` or
> `@closed` (whatever is the approved spelling) and start annotating standard
> library APIs. The annotation will be purely future-proofing and have no
> functional effect (i.e. the compiler will do nothing differently for a
> `closed enum` or `@closed public enum` (as the case may be) versus a plain
> `public enum`).
> 2. In Swift 4.1, _warn_ if switch statements over public enums don't have a
> `default` statement: offer a fix-it to insert `default: fatalError()` and, if
> the enum is in the same project, offer a fix-it to insert `closed` or
> `@closed`.
Why do you say "if the enum is in the same project, offer a fix-it to insert
`closed`? If the enum is in the same project we can perform an exhaustive
switch regardless of its public API contract (except for `open` enums if we
decide to add those).
> 3. In Swift 5, upgrade the warning to an error for non-exhaustiveness if a
> switch statement over a public enum doesn't have a `default` statement. Now,
> new syntax to extend an `open enum` can be introduced and the compiler can
> treat closed and public enums differently.
If the community and core team support this strategy I will also. It seems
reasonable and speeds up the transition by using the point release. That's a
great idea!
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution