> On Feb 8, 2017, at 7:02 PM, Adrian Zubarev via swift-evolution 
> <[email protected]> wrote:
> 
> Hurray, I cannot wait to get the consistent behavior of open/public 
> protocols. I’m not sure I could follow the idea behind the proposed closed 
> keyboard/access modifier. It almost felt like closed == final public, am I 
> mistaken something here?
> 


The best way to think about `closed` is to just think about `public enum` as it 
exists today.  The contract implies that no new cases will be added because 
user code is allowed to perform exhaustive switch over the cases.  Adding a 
case is a breaking change.

The insight behind this proposal is that we can also support these semantics 
with classes and protocols.  A `closed` class may have subclasses within the 
module so long as they are public, but no direct subclasses may be added in 
future versions (descendants may still be added if the `closed` class has a 
non-final subclass).  Adding a subclass in the future becomes a breaking change.

Similarly with protocols, no new conformances will be added in future versions 
of the library.  As with `closed` classes, if a non-final class conforms to a 
`closed` protocol subclasses may still be added in the future.

The relationship between `closed` and `final public` is interesting.  `final 
public` is equivalent to `final closed` under this proposal as long as the 
semantics of `final` implies not only that there are no subclasses *right now*, 
but also that  that no subclasses will ever be added *in the future*.  This is 
the semantics suggested in the Library Evolution document - "final may not be 
removed from a class or its members. (The presence of final enables 
optimization.)”.  This is a degenerate case of `closed`: new subclasses cannot 
be added because `final` declares that there will *never be* any subclasses.
> Furthermore, I really would love if the community could revisit how 
> open/public really should behave. When open was implemented and I tried it 
> out without reading the proposal first I bumped into things like open init() 
> which felt really odd. I understand the argumentation from the proposal, but 
> it feels wrong and inconsistent to me.
> 
> Here’s how I would have imagined open vs. public. IMHO public should really 
> mean, you cannot subclass, conform or override something in module B from 
> module A.
> 
> Modified samples from SE–0117:
> 
> // This class is not subclassable outside of ModuleA.
> public class NonSubclassableParentClass {
>     // This method >is not overridable outside of ModuleA.
>     public func foo() {}
> 
>     // This method is not overridable outside of ModuleA because
>     // its class restricts its access level.
>     // It is INVALID to declare it as `open`.
>     public func bar() {}
> 
>     // The behavior of `final` methods remains unchanged.
>     public final func baz() {}
> }
> 
> // This class is subclassable both inside and outside of ModuleA.
> open class SubclassableParentClass {
>      
>     // Designated initializer that is not overridable outside ModuleA
>     public init()
>      
>     // Another designated initializer that is overridable outside ModuleA
>     open init(foo: Int)
>      
>     // This property is not overridable outside of ModuleA.
>     public var size : Int
> 
>     // This method is not overridable outside of ModuleA.
>     public func foo() {}
> 
>     // This method is overridable both inside and outside of ModuleA.
>     open func bar() {}
> 
>     /// The behavior of a `final` method remains unchanged.
>     public final func baz() {}
> }
> 
> /// The behavior of `final` classes remains unchanged.
> public final class FinalClass { }
> /// ModuleB:
> 
> import ModuleA
> 
> // This is allowed since the superclass is `open`.
> class SubclassB : SubclassableParentClass {
>      
>     // Iff certain conditions are met, the superclass initializers are 
> inherited.
>     // `init` will stay `public` and won't be overridable.
>     //
>     // If the conditions are not met, then `init` is not inherited. That does 
> not
>     // mean that we can create a new designated `init` that matches it's 
> superclass's
>     // designated initializer. The behavior should be consistent, like the  
>     // superclass's function `foo` is reserved and not overridable, so is 
> `init`
>     // reserved in this case and not overridable.
>      
>     // This is allowed since the superclass's initializer is `open`
>     override init(foo: Int) {
>         super.init(foo: foo)
>     }
>      
>     init(bar: Int) {
>         // We could call a super designated initializer from here
>         super.init()
>         // or
>         super.init(foo: bar)
>     }
>      
>     // This is invalid because it overrides a method that is
>     // defined outside of the current module but is not `open'.
>     override func foo() { }
> 
>     // This is allowed since the superclass's method is overridable.
>     // It does not need to be marked `open` because it is defined on
>     // an `internal` class.
>     override func bar() { }
> }
> required should always match the same scope level as the type in which it’s 
> defined. That means if the class is open, than any of it’s required 
> initializers will be open as well.
> 
> 
> 
> 
> -- 
> Adrian Zubarev
> Sent with Airmail
> 
> Am 9. Februar 2017 um 00:49:04, Xiaodi Wu via swift-evolution 
> ([email protected] <mailto:[email protected]>) schrieb:
> 
>> 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.
>> 
>> 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.
>> On Wed, Feb 8, 2017 at 17:05 Matthew Johnson via swift-evolution 
>> <[email protected] <mailto:[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
>>  
>> <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
>> [email protected] <mailto:[email protected]>
>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> _______________________________________________
>> swift-evolution mailing list
>> [email protected] <mailto:[email protected]>
>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
> 
> 
> _______________________________________________
> swift-evolution mailing list
> [email protected] <mailto:[email protected]>
> https://lists.swift.org/mailman/listinfo/swift-evolution 
> <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