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?
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]) 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]> 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