[Pitch] consistent public access modifiers

The "@fixedContents mostly doesn't change the behavior of a struct" makes me think it is unimportant for these two annotations to have the same name.

Jordan

···

On Feb 12, 2017, at 21:52, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

Also, note that there will be at least one other similar annotation, but for structs — the evolution document calls it @fixedContents. We want a way to declare that the set of stored properties in a struct will never change, allowing clients to make assumptions about its layout. Unlike @closed enums, @fixedContents structs mostly behave the same. The one important difference is that it will be possible to define designated initializers of @fixedContents structs inside extensions from another module.

Not a huge response, but how about locked?

···

--
Adrian Zubarev
Sent with Airmail

Am 15. Februar 2017 um 20:31:30, Matthew Johnson via swift-evolution (swift-evolution@swift.org) schrieb:

On Feb 14, 2017, at 3:43 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Feb 13, 2017, at 7:45 AM, Matthew Johnson <matthew@anandabits.com> wrote:

If you look closely, when most people say “closed enum” they mean a fixed, complete set of cases that are all public. But when people say “closed protocol” they don’t actually mean a fixed, complete set of conformances that are all public. They simply mean clients cannot add conformances. This is the semantic contract of resilient enums, not closed enums.

Yes, our traditional terminology here has been a little bit confused.

What I instead suggest is that we think of a closed enum as being like a fragile (non-resilient) struct. In both cases, you are committing to a particular design for the type. So I think we should give them both the same keyword—something like:

@fixed struct Person {
var name: String
var birthDate: Date
}
@fixed enum Edge {
case start
case end
}

You omitted public here. Does that mean you intend for `@fixed` to imply public visibility? If so, I could get behind this. But I am curious why you made it an attribute rather than a keyword.

No, I'm sorry, I meant to say `@fixed public struct` and `@fixed public enum`. I don't think `@fixed` implies public-ness, either, so it would need to be paired with a `public` keyword. There *may* be keywords we could use that would, like `exposed`, but I'm not sure we want to make this feature so prominent, and I'm not sure how that would work with classes you want to both expose and permit subclassing of. (Would that be `exposed open class Foo`?)

I thought of a couple of other possible keywords. I had previously mentioned `complete` as an alternative to `closed`. Another option that goes in this direction is `total` - it borrows from the mathematical notion of a total function. I’m not sure how I feel about this option and it still doesn’t necessarily imply “more public than public” very strongly (only in the sense that its totality is known to all which is a rather weak sense).

Another possibility is `transparent`. This does imply “more public than public” a some sense that is similar to `open`. I know `@transparent` (or something like that) has been used as an unsupported(?) attribute hinting to the compiler that a function should be made available for inlining (what is the current status of this?). The meaning of this attribute is in some ways similar to the meaning you ascribe to `fixed`. In all cases, this is only meaningful for `public` entities so it feels like a promising direction.

The one thing that makes me somewhat uncomfortable with this approach is that in the context of structs and functions it has no semantic impact on user code - it is only an optimization, while for enums it wold make a important semantic difference to user code. I’m not sure we should use the same syntax for something that is sometimes an optimization and sometimes is semantically meaningful. This concern is relevant regardless of what the keyword is called.

I don't see it mentioned here (maybe I just missed it), but even though we *could* do exhaustiveness checking on non-open protocols, I'm not convinced that's a good idea. Usually when you have several types conforming to a protocol, you should access type-specific behavior through polymorphism, not by switching on the protocol. A protocol is supposed to represent a behavior, not just mark a type in some arbitrary way.

I agree that you should usually be adding polymorphism, but preventing exhaustive switch on what is effectively a style argument seems like an unnecessary restriction to me. There will be times when it could be used to good effect. I think the community has done a pretty good job of figuring out how to use Swift’s many features well and don’t believe it would be frequently abused.

I agree we shouldn't change the language to *prevent* bad style. But this would go beyond that—we'd be putting specific engineering effort solely into *enabling* bad style. At minimum, this should fall so far down our to-do list that we'll probably never get to it.

I still support this general approach. One spelling could simply be `@nonopen`. Although if we don't use `closed`, we could simply use `@closed` like I suggested—here it really *would* be an antonym to `open`.

I like the idea of using `@nonopen` for the transitional attribute. Both because it “removes the openness” that `public protocol` currently implies. In that sense it is probably the most accurate term we could find and it’s also pretty concise.

It also sounds a little bit awkward, which is normally a reason not to use it, but perhaps that's actually a good thing in a temporary, transitional keyword.

A similar mult-release strategy would work for migrating public enums.

What is it that needs migrating here? Lack of exhaustiveness checking? It sounds like we were planning to break that anyway in some fashion.

Public enums are not currently resilient. Clients are allowed to switch over them without a `default` clause. This means that client code will fail to compile in a version of Swift where `public enum` has the resilient contract unless the library changes to adopt closed semantics or the client adds a default case.

My thinking was that, since most existing `public` enums should probably not be `@fixed`, we should just change the behavior and let some switch statements break. Most `public` protocols, on the other hand, ought to become `open`, so we should flag that change and require an explicit marker like `@nonopen` if you really don't want to change over. But I could be convinced otherwise.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

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.

Certainly, `open` and your proposed `closed` both represent contracts among library authors, users, and the compiler. But so do other features that are not access modifiers--unless your intent is to make all the (not yet totally finalized) resilience attributes such as `@inlineable` become access modifiers. That both `open` and the proposed `closed` are both contracts doesn't make them both access modifiers.

This is a good point. But `open`, `closed` and `public` have something very important in common: they all have meaning with regarding the set of cases, subclasses or conforming types and are mutually exclusive in the contract they offer in this regard.

To me, the reason one _could_ justify `open` being spelled like an access modifier (which I was not supportive of, actually, though I was fine with `public` not allowing subclassing) is that it quacks like an access modifier in some ways. In particular, since it offers more "access" to a class than does `public` by allowing subclassing, one can argue that it fits at the top of a hierarchy of access levels.

As you define it, `closed` also makes additional guarantees to the end user than does `public` by (self-)imposing restrictions on the library author. Thus, it does not fit into a hierarchy of access modifiers where each level is more "accessible" than the next. Put another way, my point here is that `closed` is not the opposite of `open` in key ways, as the names might suggest. In fact, from the perspective of a library user, both `closed` and `open` would allow you to do more than `public`.

You make a great point here when you say that `closed` makes additional guarantees to the end user, beyond `public`. `closed` and `open` both give users more capabilities by placing additional burden on the library. In a very real sense `closed` *does* provide more visibility to information about the type - it guarantees knowledge of the *complete* set of cases, subclasses or protocols both now and in the future (modulo breaking changes). In this sense it can be said to be “more accessible than public”. This does form a strict hierarchy, just not a linear one:

            private
                  >
          fileprivate
                  >
            internal
                  >
             public
            / \
closed open

Yes, I think we are now on the same page as to the mental model here. As to whether a non-linear hierarchy is desirable or not, that's a judgment call.

Great!

Of course, everything in language design is ultimately a judgement call. My opinion is that a non-linear hierarchy turns out to be a natural consequence of the decision we made regarding `open`. Adopting this approach has a compelling and consistent logic that other approaches won't have as long as `open` is an access modifier.

I think this is a good thing - it means the language doesn't syntactically favor or penalize any of `public`, `open` and `closed`. Instead it requires an explicit choice between them and gives them equal syntactic weight.

A reasonable case can be made that `open` and `closed` shouldn't be access modifiers. But I feel like that ship sailed when `open` was introduced.

Personally, I feel like we made the best trade off possible and the same logic is relevant to the `closed` discussion.

Most importantly, I think we should strive for consistency. It is currently lacking and would get worse if `@closed` were introduced as an attribute.

I admit that the name `closed` doesn’t *sounds* more accessible. Maybe there is a better name? But `closed` is the name we have all been using for this contract and it offers a nice symmetry with open. One opens the set of cases, subclasses, or conforming types to users. The other closes off the ability of the library to add to or hide any members of the set of cases, subclasses or conforming types. The symmetry is in the fact that they both say something about the totality of the set of cases, subclasses or conforming types.

It’s also worth noting that a `public` type can become `closed` or `open` in a future version of a library, but once `closed` or `open` that option is fixed forever (modulo breaking changes). This also suggests the hierarchy I visualized above.

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.

Since the additional guarantees of both `open` and your proposed `closed` impose burdens on the library _author_ and offer more flexibility to the library _user_, I feel it is highly misleading to make them co-equal but antonyms. They are not the "opposite" of each other and the spelling would be misleading.

They are not exactly antonyms, but if you think about this in terms of the set of cases, subclasses or conforming types their obvious meaning does make sense. `closed` says to users: “you know the complete set”. `open` says to users: “you are allowed to add to the set”.

I suppose this suggests a possible alternative name: `complete`. That would avoid the antonym relationship and maybe be more accurate.

IMHO: nah, if we're comfortable with a non-linear hierarchy as you've drawn above, `open` and `closed` are fine; if we're not, then no renaming will fix that. I don't think it's the name, per se; I think my main qualm is with the necessarily non-linear nature of the hierarchy (and with the proliferation of access levels).

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.

Yes, this is totally fair.

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?

Yes, I am.

This has the effect of defaulting to `public` behavior. It will lead to unnecessary boilerplate-y annotations in our code. One of the big drivers behind the decision to make `open` an access modifier is to avoid this kind of boilerplate-y annotation. Why should we have to write `@closed public` when simply saying `closed` (or `complete`) unambiguously communicates the same thing?

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`.

I would not characterize `open` as a neutral stance. But that's neither here nor there.

What I meant by “neutral” is that both `open` and `public` carry the same syntactic weight. The language does not make one or the other more convenient for the library author and requires them to make an explicit choice.

The problem (as I see it) with your argument is that, in general, the following two thoughts are incompatible: (a) the additional burden of a public API contract should be opt-in; vs. (b) there should be neutrality as to whether or not one assumes the burden of a particular public API contract.

Opting in means that one has made the deliberate effort of rejecting some sort of more natural or default choice. Writing `@closed public enum` can be easily thought of as opting in, because doing so very clearly requires actively choosing to add something more than the alternative `public enum`.

Writing `open class` is harder to justify as opting in, because it is not as obvious that `public class` is some sort of default. (Hence, why I did not think that `open` should have been an access level, though I was fine with the proposal otherwise.) The saving grace there is that, in the linear hierarchy of access levels, `open` is two steps away from the default access level of `internal`, whereas `public` is only one step removed. So, one can make the passable rationalization that in choosing `open` one is escalating from `internal` to `public` to `open`--i.e., that going the extra step can be regarded as the act of opting in by choosing not to stop at `public`.

As I see it, one can only be said to opt in to B (vs. an alternative A) only to the extent that A and B are not neutral choices but rather in some sort of hierarchy. Again, I'm unconvinced `closed` fits into a linear hierarchy of access modifiers, and therefore I see spelling `closed` like `open` as problematic.

I think part of our disagreement is in the definition of opting in. What *I* mean by that is that the compiler does not make a choice for the user because they omitted an annotation. We require a class visible outside the module to chooses `open` or chooses `public`. By making that choice it opts-in to one semantic or the other. There is no "deliberate effort to reject a more natural choice” required at all. All that is required is that intent is unambiguous.

Well here I think we have diametrically opposite views on what is opt-in. FWIW, I do believe that when most people in the community say that Swift makes public API commitments opt-in, they intend that statement to mean that the user must make a deliberate effort to override the compiler default of not making a public API commitment, not the diametrically opposite view that the compiler has no default choice.

Ok, does it work for you if everywhere I have said "opt-in" we substitute "explicitly stated intent"?

After all, the default access level in Swift 3 when no access modifier is specified is `internal`; I was one of the people on this list who made that suggestion and was overjoyed to see it adopted.

I agree. This was the right decision.

However, the same logic you used for `open` also works for `closed` using the hierarchy I visualized above. I find the logic of that hierarchy very convincing (regardless of the final name we choose for `closed`).

I dislike intensely the contortions I had to go through to justify `open` as a spelling, and I'd be a little sad to see such reasoning propagate further. But at the end of the day, I think if we go in with eyes open and explicitly accept or reject a non-linear access modifier scheme, it'll work out OK.

If you are still uncomfortable with `open` as an access level I can understand why you don't want to proceed further in that direction.

It seemed a little bit odd at first, but I think it accomplishes two very important things. It requires library authors to explicitly choose a contract regarding how they type may be "refined" (does this work as a general term to refer to cases, subclasses, and protocols?). And it does this without favoring or penalizing any of the options syntactically.

This second point received quite a bit of consideration in the review of `open`. There is a strong desire to avoid having Swift grow a bunch of annotations that lead to code feeling boilerplate-y. Making `open` an access modifier accomplished both of these goals very well IMO.

···

Sent from my iPad

On Feb 10, 2017, at 9:35 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Fri, Feb 10, 2017 at 9:15 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 10, 2017, at 1:06 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Thu, Feb 9, 2017 at 9:57 AM, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 8, 2017, at 5:48 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

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 <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\) 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
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

That makes actually sense to me if we think of it that we never will get any sub-typing for enums. I’m still undecided on that sub-typing topic about value types. Just out of curiosity it would be interesting to hear from the core team if this would be a future direction for Swift or not, be it in Swift 20 or whatever. :)

The core team has indicated support for value subtyping. It's unclear when this might happen though.

I am working on writing up some ideas related to this right now. I hope that helps you to see how value subtyping fits into the hypothetical Swift with `closed` as an access modifier I am proposing. This will be more of a "manifesto" style document than a proposal. It will lay out the landscape a bunch of related ideas, some of which may have ABI impact (and this be relevant for consideration now) and others will be left for future consideration.

Back on the original topic: If the community feels we need something like closed, then so be it, I don’t mind having flexibility because it makes the language more powerful.

We definitely need it for enums. The only question is how we spell it and whether the semantics have a clear and consistent relationship with how we treat classes, protocols and the `open` access modifier. My proposal is that we do it in a way that is clear and consistent.

···

Sent from my iPad

On Feb 11, 2017, at 12:38 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

--
Adrian Zubarev
Sent with Airmail

Am 11. Februar 2017 um 19:31:10, Xiaodi Wu (xiaodi.wu@gmail.com) schrieb:

I think Matthew's point (with which I agree) is that, as enums are sum types, adding or removing cases is akin to subclassing. You can extend a public enum by adding methods just like you can extend a public class. But just as you cannot subclass a public class, you should not be able to add or remove cases from a public enum.

On Sat, Feb 11, 2017 at 8:37 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:
I have to correct myself here and there.

… which would be extensible if that feature might be added to swift one day.

Again, I see open only as a contract to allow sub-typing, conformances and overriding to the client, where extensibility of a type a story of it’s own.

--
Adrian Zubarev
Sent with Airmail

Am 11. Februar 2017 um 15:33:17, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

It wasn’t my intention to drive to far way off topic with this. The major point of my last bike shedding was that I have to disagree with you about the potential future open enum vs. public enum and closed enum.

public today does not add any guarantee to prevent the client from extending your type. For instance:

// Module A
public class A { public init() {} }

// Module B
extension A {
       
    convenience init(foo: Int) {
        print(foo)
        self.init()
    }
}
That also implies to me that open as an access modifier does not prevent extensibility.

Speaking of opened enums, we really do not mean open enum to allow extensibility where closed enum would mean the opposite. closed or @closed by all the definitions I’ve read so far is what the current public means for enums. If this is going to be fixed to closed enum (@closed public enum) than what we’re currently speaking of is nothing else than public enum, which would be extensible if that future might be added to swift one day.

Again, I see open only as a contract to prevent sub-typing, conformances and overriding, where extensibility of a type a story of it’s own.

Quickly compared to protocols: public-but-not-open protocol from module A should remain extensible in module B. Consistently that would mean that public enum is the enum when we’re talking about future extensibility of that enum from the clients side outside your module. You simply should be able to add new cases directly to your enum if it’s not annotated as closed. open enum on the other hand makes only sense when we’d speak about sub-typing on enums or value types in general.

--
Adrian Zubarev
Sent with Airmail

Am 11. Februar 2017 um 14:08:02, Matthew Johnson (matthew@anandabits.com) schrieb:

Sent from my iPad

On Feb 11, 2017, at 4:25 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

I’m probably better describing things with some bikeshedding code, but feel free to criticize it as much as you’d like.

//===========--------- Module A ---------===========//
@closed public enum A {
    case a
}

extension A {
    case aa // error, because enum is closed
}

This is an error because you can't add cases in an extension. I imagine this is how cases would be added outside the module if we allow `open enum` in the future. But whether or not this is allowed *within* the module is a separate question that is orthogonal to `closed` and `open`.

public func foo(a: A) {
    switch a {
    case .a:
        print("done")
    }
}

public enum B {
    case b
}

extension B {
    case bb // fine, because not-closed enums are extensible
}

As noted above, whether this is allowed or not *within* the module is orthogonal to `closed`. *Outside* the module it would only be possible for enum declared `open` (if we add this feature in the future).

public func bar(b: B) {
    switch b {
    case .b:
        print("b")

    default: // always needed
        print("some other case")
    }
}

// Sub-enum relationships

// Possible even the enum A is closed, because `@closed` only
// closes the extensibility of an enum
enum SubA : A {
    case aa
}

Now you're talking about value subtypes. That is orthogonal. Also, this syntax already has a meaning (the raw value of the enum is A) so we wouldn't be able to use it the way you are intending here. Finally, it is misleading syntax because what you mean here is "A is a subtype of SubA" which is exactly the opposite of what the syntax implies.

All values of A are valid values of SubA, but SubA has values that are not valid values of A.

// The following enum can have a sub-enum in the clients module
open enum C {
    case c
}
public func cool(c: C) {
    switch c {
    case .c:
        print("c")

    default: // always needed
        print("some other case")
    }
}

@closed open enum D {
    case d
}

public func doo(d: D) {
    switch b {
    case .b:
        print("b")
    }
}

// The enum case is always known at any point, no matter
// where the instance comes from, right?

let subA = SubA.aa
let otherSubA = SubA.a // Inherited case

let a: A = subA // error, downgrade the sub-enum to A first
let a: A = otherSubA // okay

foo(a: subA) // error, downgrade the sub-enum to A first
foo(a: otherSubA) // okay

//===========--------- Module B ---------===========//

// Totally fine
switch A.a {
case .a:
    print("done")
}

extension A {
    case aa // not allowed because the enum is closed
}

extension B {
    case bbb
}

switch B.b {
case .b:
    print("b")
default:
    print("somethine else")
}

bar(b: B.bbb) // fine, because the switch statement on enums without
// `@closed` has always`default`

// Allowed because `C` is open, and open allows sub-typing, conforming
// and overriding to the client
enum SubC : C {
    case cc
}

let subC = SubC.cc

cool(c: subC) // okay

enum SubD : D {
    case dd
}

doo(d: D.dd)// error, downgrade sub-enum to D first
My point here is, that we should not think of (possible) open enums as enums that the client is allowed to extend. That way we’re only creating another inconsistent case for the open access modifier. As far as I can tell, open as for today means “the client is allowed to subclass/override things from a different module”.

Yes, but subclasses are analogous to enum cases. A subtype of an enum would remove cases. I think you are misunderstanding the relationship of enums to classes and protocols.

And I already said it hundred of times that we should extend this to make open a true access modifier in Swift. That said the meaning of open should become:

The client is allowed to sub-type (currently only classes are supported).
The client is allowed to conform to open protocols
The client is allowed to override open type members
This also means that extensibility is still allowed to public types. Public-but-not-open classes are still extensible today, which is the correct behavior. Extending an enum which is not closed could or probably should be made possible through extensions, because I cannot think of anther elegant way for the client to do so.

This is what `open enum` would allow. It is the proper enum analogue of open classes.

That will leave us the possibility to think of sub-typing enums in the future (I sketched it out a little above).

Value subtyping is very interesting. I have been working on some ideas around this but I want to keep this thread focused.

If I’m not mistaken, every enum case is known at compile time,

This is true today but will not always be true in the future. That is in large part what this thread is about.

which means to me that we can safely check the case before allowing to assign or pass an instance of a sub-enum to some of its super-enum. (Downgrading an enum case means that you will have to write some code that either mutates your current instance or creates a new one which matches one of the super-enum cases.) Furthermore that allows a clear distinction of what open access modifier does and how @closed behaves.

I'm not going to comment on the rest because it is premised on a misunderstanding of what value subtyping is. I'm going to share some ideas around value subtyping in a new thread as soon as I have a chance to finish putting them together.

To summarize:

@closed enum - you’re not allowed to add new cases to the enum in your lib + (you’re allowed to create sub-enums)
@closed public enum - you and the client are not allowed to add new cases (+ the client is not allowed to create sub-enums)
@closed open enum - you and the client are not allowed to add new cases (+ the client might create new sub-enums)
enum - you’re allowed to add new cases (default is needed in switch statements) (+ you can create new sub-enums)
public enum - you and the client are allowed to add new cases (+ only you are allowed to create new sub-enums)
open enum - you and the client are allowed to add new cases (everyone can create new sub-enums)
This is a lot of bike shedding of mine, and the idea might not even see any light in Swift at all, but I’d like to share my ideas with the community. Feel free to criticize them or flesh something out into something real. :)

P.S.: If we had something like this:

@closed enum X {
    case x, y
    func foo() {
     switch self {
        case .x, .y:
            print("swift")
    }
}

enum Z : X {
    case z, zz
    override func foo() {
        // Iff `self` is `z` or `zz` then calling super will result in an error.
        // Possible solution: always tell the client to downgrade explicitly the
        // case first if there is an attempt to call super (if mutating),
        // or handle all cases

        switch self {
        case .z, .zz:
            print("custom work")
        default: // or all super-enum cases
            super.foo()
        }
    }
}

--
Adrian Zubarev
Sent with Airmail

Am 11. Februar 2017 um 04:49:11, Xiaodi Wu via swift-evolution (swift-evolution@swift.org) schrieb:

On Wed, Feb 8, 2017 at 5:05 PM, 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\) 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'll leave aside closed protocols, which as per Jordan Rose's feedback may or may not have sufficient interestingness.
- 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`.
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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I’m definitely looking forward to this, it’s a highly interesting topic to follow. :) Thank you for all your hard work.

···

--
Adrian Zubarev
Sent with Airmail

Am 11. Februar 2017 um 19:47:36, Matthew Johnson (matthew@anandabits.com) schrieb:

Sent from my iPad

On Feb 11, 2017, at 12:38 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

That makes actually sense to me if we think of it that we never will get any sub-typing for enums. I’m still undecided on that sub-typing topic about value types. Just out of curiosity it would be interesting to hear from the core team if this would be a future direction for Swift or not, be it in Swift 20 or whatever. :)

The core team has indicated support for value subtyping. It's unclear when this might happen though.

I am working on writing up some ideas related to this right now. I hope that helps you to see how value subtyping fits into the hypothetical Swift with `closed` as an access modifier I am proposing. This will be more of a "manifesto" style document than a proposal. It will lay out the landscape a bunch of related ideas, some of which may have ABI impact (and this be relevant for consideration now) and others will be left for future consideration.

Back on the original topic: If the community feels we need something like closed, then so be it, I don’t mind having flexibility because it makes the language more powerful.

We definitely need it for enums. The only question is how we spell it and whether the semantics have a clear and consistent relationship with how we treat classes, protocols and the `open` access modifier. My proposal is that we do it in a way that is clear and consistent.

--
Adrian Zubarev
Sent with Airmail

Am 11. Februar 2017 um 19:31:10, Xiaodi Wu (xiaodi.wu@gmail.com) schrieb:

I think Matthew's point (with which I agree) is that, as enums are sum types, adding or removing cases is akin to subclassing. You can extend a public enum by adding methods just like you can extend a public class. But just as you cannot subclass a public class, you should not be able to add or remove cases from a public enum.

On Sat, Feb 11, 2017 at 8:37 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:
I have to correct myself here and there.

… which would be extensible if that feature might be added to swift one day.

Again, I see open only as a contract to allow sub-typing, conformances and overriding to the client, where extensibility of a type a story of it’s own.

--
Adrian Zubarev
Sent with Airmail

Am 11. Februar 2017 um 15:33:17, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

It wasn’t my intention to drive to far way off topic with this. The major point of my last bike shedding was that I have to disagree with you about the potential future open enum vs. public enum and closed enum.

public today does not add any guarantee to prevent the client from extending your type. For instance:

// Module A
public class A { public init() {} }

// Module B
extension A {
        
    convenience init(foo: Int) {
        print(foo)
        self.init()
    }
}
That also implies to me that open as an access modifier does not prevent extensibility.

Speaking of opened enums, we really do not mean open enum to allow extensibility where closed enum would mean the opposite. closed or @closed by all the definitions I’ve read so far is what the current public means for enums. If this is going to be fixed to closed enum (@closed public enum) than what we’re currently speaking of is nothing else than public enum, which would be extensible if that future might be added to swift one day.

Again, I see open only as a contract to prevent sub-typing, conformances and overriding, where extensibility of a type a story of it’s own.

Quickly compared to protocols: public-but-not-open protocol from module A should remain extensible in module B. Consistently that would mean that public enum is the enum when we’re talking about future extensibility of that enum from the clients side outside your module. You simply should be able to add new cases directly to your enum if it’s not annotated as closed. open enum on the other hand makes only sense when we’d speak about sub-typing on enums or value types in general.

--
Adrian Zubarev
Sent with Airmail

Am 11. Februar 2017 um 14:08:02, Matthew Johnson (matthew@anandabits.com) schrieb:

Sent from my iPad

On Feb 11, 2017, at 4:25 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

I’m probably better describing things with some bikeshedding code, but feel free to criticize it as much as you’d like.

//===========--------- Module A ---------===========//
@closed public enum A {
    case a
}

extension A {
    case aa // error, because enum is closed
}
This is an error because you can't add cases in an extension. I imagine this is how cases would be added outside the module if we allow `open enum` in the future. But whether or not this is allowed *within* the module is a separate question that is orthogonal to `closed` and `open`.

public func foo(a: A) {
    switch a {
    case .a:
        print("done")
    }
}

public enum B {
    case b
}

extension B {
    case bb // fine, because not-closed enums are extensible
}
As noted above, whether this is allowed or not *within* the module is orthogonal to `closed`. *Outside* the module it would only be possible for enum declared `open` (if we add this feature in the future).

public func bar(b: B) {
    switch b {
    case .b:
        print("b")

    default: // always needed
        print("some other case")
    }
}

// Sub-enum relationships

// Possible even the enum A is closed, because `@closed` only
// closes the extensibility of an enum
enum SubA : A {
    case aa
}

Now you're talking about value subtypes. That is orthogonal. Also, this syntax already has a meaning (the raw value of the enum is A) so we wouldn't be able to use it the way you are intending here. Finally, it is misleading syntax because what you mean here is "A is a subtype of SubA" which is exactly the opposite of what the syntax implies.

All values of A are valid values of SubA, but SubA has values that are not valid values of A.

// The following enum can have a sub-enum in the clients module
open enum C {
    case c
}
public func cool(c: C) {
    switch c {
    case .c:
        print("c")

    default: // always needed
        print("some other case")
    }
}

@closed open enum D {
    case d
}

public func doo(d: D) {
    switch b {
    case .b:
        print("b")
    }
}

// The enum case is always known at any point, no matter
// where the instance comes from, right?

let subA = SubA.aa
let otherSubA = SubA.a // Inherited case

let a: A = subA // error, downgrade the sub-enum to A first
let a: A = otherSubA // okay

foo(a: subA) // error, downgrade the sub-enum to A first
foo(a: otherSubA) // okay

//===========--------- Module B ---------===========//

// Totally fine
switch A.a {
case .a:
    print("done")
}

extension A {
    case aa // not allowed because the enum is closed
}

extension B {
    case bbb
}

switch B.b {
case .b:
    print("b")
default:
    print("somethine else")
}

bar(b: B.bbb) // fine, because the switch statement on enums without
// `@closed` has always`default`

// Allowed because `C` is open, and open allows sub-typing, conforming
// and overriding to the client
enum SubC : C {
    case cc
}

let subC = SubC.cc

cool(c: subC) // okay

enum SubD : D {
    case dd
}

doo(d: D.dd)// error, downgrade sub-enum to D first
My point here is, that we should not think of (possible) open enums as enums that the client is allowed to extend. That way we’re only creating another inconsistent case for the open access modifier. As far as I can tell, open as for today means “the client is allowed to subclass/override things from a different module”.

Yes, but subclasses are analogous to enum cases. A subtype of an enum would remove cases. I think you are misunderstanding the relationship of enums to classes and protocols.

And I already said it hundred of times that we should extend this to make open a true access modifier in Swift. That said the meaning of open should become:

The client is allowed to sub-type (currently only classes are supported).
The client is allowed to conform to open protocols
The client is allowed to override open type members
This also means that extensibility is still allowed to public types. Public-but-not-open classes are still extensible today, which is the correct behavior. Extending an enum which is not closed could or probably should be made possible through extensions, because I cannot think of anther elegant way for the client to do so.

This is what `open enum` would allow. It is the proper enum analogue of open classes.

That will leave us the possibility to think of sub-typing enums in the future (I sketched it out a little above).

Value subtyping is very interesting. I have been working on some ideas around this but I want to keep this thread focused.

If I’m not mistaken, every enum case is known at compile time,

This is true today but will not always be true in the future. That is in large part what this thread is about.

which means to me that we can safely check the case before allowing to assign or pass an instance of a sub-enum to some of its super-enum. (Downgrading an enum case means that you will have to write some code that either mutates your current instance or creates a new one which matches one of the super-enum cases.) Furthermore that allows a clear distinction of what open access modifier does and how @closed behaves.

I'm not going to comment on the rest because it is premised on a misunderstanding of what value subtyping is. I'm going to share some ideas around value subtyping in a new thread as soon as I have a chance to finish putting them together.

To summarize:

@closed enum - you’re not allowed to add new cases to the enum in your lib + (you’re allowed to create sub-enums)
@closed public enum - you and the client are not allowed to add new cases (+ the client is not allowed to create sub-enums)
@closed open enum - you and the client are not allowed to add new cases (+ the client might create new sub-enums)
enum - you’re allowed to add new cases (default is needed in switch statements) (+ you can create new sub-enums)
public enum - you and the client are allowed to add new cases (+ only you are allowed to create new sub-enums)
open enum - you and the client are allowed to add new cases (everyone can create new sub-enums)
This is a lot of bike shedding of mine, and the idea might not even see any light in Swift at all, but I’d like to share my ideas with the community. Feel free to criticize them or flesh something out into something real. :)

P.S.: If we had something like this:

@closed enum X {
    case x, y
    func foo() {
     switch self {
        case .x, .y:
            print("swift")
    }
}

enum Z : X {
    case z, zz
    override func foo() {
        // Iff `self` is `z` or `zz` then calling super will result in an error.
        // Possible solution: always tell the client to downgrade explicitly the
        // case first if there is an attempt to call super (if mutating),
        // or handle all cases

        switch self {
        case .z, .zz:
            print("custom work")
        default: // or all super-enum cases
            super.foo()
        }
    }
}

--
Adrian Zubarev
Sent with Airmail

Am 11. Februar 2017 um 04:49:11, Xiaodi Wu via swift-evolution (swift-evolution@swift.org) schrieb:

On Wed, Feb 8, 2017 at 5:05 PM, 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\) 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 (swift/LibraryEvolution.rst at main · apple/swift · GitHub) 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'll leave aside closed protocols, which as per Jordan Rose's feedback may or may not have sufficient interestingness.
- 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`.
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.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

For example, the compiler squashes the layout of an enum in to the smallest type which can represent all of its cases - so if you only have 2 cases, your enum will be an Int1 (possibly stored in a way which overlaps the payload, if there are spare bits to do so).

This is still the case with resilient enums. The current implementation of resilient value types does not change the memory layout of those types. Rather, it makes clients access the values indirectly, using a value witness table instead of making assumptions about the size, etc.

If we introduce some kind of forwards-compatible enum, it should explicitly be something different (like an “open” enum). It’s the nichest of niche-cases, would change the layout and prohibit optimisations, and make those objects more difficult to reason about.

Perhaps, but note that resilience does *not* prohibit layout optimizations — instead, it means that clients use an indirect access pattern where knowledge of the layout is encapsulated in the library.

Slava

···

On Feb 11, 2017, at 2:41 PM, Karl Wagner via swift-evolution <swift-evolution@swift.org> wrote:

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
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Sent from my iPad

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.

Sure; I was specifically considering the phased introduction of `closed`. It's been a while since I've thought about how to phase in a change regarding public protocols and open protocols.

That said, others make good points about _conforming to_ protocols by a type vs. _refining_ protocols by another protocol, and whether either or both of these is more akin to subclassing a class.

This is something that was in the back of my mind for months (I’ve thought about this off and on since last summer). My conclusion is that *conforming* is the important relationship, at least in terms of the `open`, and `closed` discussion.

As I mentioned in my reply to Karl, I can’t think of any benefit that would be afforded to either a library or its clients by restricting refinement. Obviously clients get more flexibility if they *can* refine protocols defined by a library. From the perspective of a library author nothing changes if a client refines a protocol it defines. All of the semantics of the code in the library is identical either way, as is it’s options for future evolution.

Yeah that’s fine, I was just saying that any proposal should make it clear that those sub-protocols are closed externally (I don’t really like the term “refinements” — those sub-protocols may add new requirements). Perhaps we should require an explicit “closed” annotation when deriving a sub-protocol from a closed protocol in another module?

Oh, I see what you’re saying. I don’t think they should be required to add `closed`, no. Maybe there are 6 types that conform to the super protocol and 2 that conform to the sub-protocol in one version of a library. In a future version one of the other 4 could be made to conform to a sub-protocol without a breaking change if the sub-protocol is not required to be closed. There is an upper bound placed on the number of possible conformances but the subset of candidate types that actually conform need not be closed.

Like it or not, isn’t the standard terminology in the community to say that a sub-protocol refines a super-protocol?

- 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).

Hmm, well now I'm not in favor of my own suggestion. A public enum, though it may gain or lose cases in future versions, can be exhaustively switched over in the present whether it's same-module or third-party. No warning or error should issue on attempting to switch over a public enum without a default case.

This is true for the current semantics of `public enum`. But what I am suggesting is that this semantic be called `closed enum`. `public enum` would allow libraries to add new cases resiliently. This is the same semantic for `public enum` that is mentioned in the Library Evolution document (which spells my `closed enum` as `@closed public enum`).

We have to require a default case for resilient enums because the client may run against a future version of the library with a new case. I think a couple people have mentioned either allowing an implicit default case with `break` or `fatalError` to be synthesized but I am strongly opposed to this. The only other option is a compiler error for a switch over a resilient enum that does not have a default clause.

The Library Evolution document isn’t gospel; it’s more like a collection of musings and aspirations, so I don’t think we should be bound by the syntax it uses.

Yes, obviously I agree with this or I wouldn’t be proposing something different! :)

WRT your point earlier that enums are not suitable for a mutually-exclusive set of options and that structs should be used instead - I think you have it backwards. That is precisely what enums *are* suitable for. For example, FloatingPointSign and FloatingPointRoundingMode in the standard library. If you want to represent something looser - say an integer which _could_ take any value, but has a few particular salient values, that would best be expressed in a wrapper struct with an embedded enum for the salient values.

I didn’t argue that enums aren’t suitable here. Precisely the opposite. I said that while one can make an argument in that regard (because the library often does not intend for users to switch over this kind of enum) there are good reasons to choose to use an enum.

I don’t believe that forwards-compatible enums are that vital to the conceptual model that they should be the default, in fact I think they are very conceptually different to how enums are used today and what they were designed for. For example, the compiler squashes the layout of an enum in to the smallest type which can represent all of its cases - so if you only have 2 cases, your enum will be an Int1 (possibly stored in a way which overlaps the payload, if there are spare bits to do so). Making your enum raw-representable as an Int32 or whatever doesn’t actually have any effect on this - an enum of 100 Int32-valued cases will get represented as an Int8; they were not designed to carry arbitrary numbers of additional cases. Conceptually, too, enums are easy to reason about because they can only have one out of a set of known values -- knowing that an objects ’state’ variable is one of { notStarted, running, finished } is what separates an enum from an actual Integer. The truth is there is only so many changes you can make to an enum before you invalidate the reasoning of client code.

As for backwards-compatibility, as I said before, I believe that is best done with versioning. So library authors can add/remove cases, and application developers can follow along while supporting systems that are stuck on older library versions (e.g. Foundation).

If we introduce some kind of forwards-compatible enum, it should explicitly be something different (like an “open” enum). It’s the nichest of niche-cases, would change the layout and prohibit optimisations, and make those objects more difficult to reason about.

I generally agree with you about the value of keeping enums closed for clients of a library where that is feasible. That is actually part of the motivation behind my proposal. While we see the value in this, it *does* represent a stronger API contract that restricts future versions of the library.

For Apple who needs to be able to run old apps on new versions of its systems making this a default is problematic. Swift has intentionally adopted the approach of requiring explicit intent to be stated for every commitment a module makes. I suspect this is why the Library Evolution document suggest changing the semantics of `public enum` and uses an annotated `@closed public enum` to opt in to the stronger API contract.

My sense is that there is a general consensus that we *do* need resilient enums and that if there is a default this will be the default. What my proposal aims to do is to build on the work that was done with `open` to put `closed` on equal footing with `public` and `open`, requiring an explicit statement of intent. Requiring an annotation for `@closed` has two negative consequences. It is unnecessary boilerplate and a naked `public enum` is not as explicit statement of intent to the degree that mutually exclusive annotation are: `@closed` may be omitted by accident. Responsible library authors should not make that mistake, but nevertheless it is less explicit and less clear than mutually exclusive annotations would be.

···

On Feb 11, 2017, at 4:41 PM, Karl Wagner <razielim@gmail.com> wrote:

On 11 Feb 2017, at 20:50, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Feb 11, 2017, at 12:40 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:
On Sat, Feb 11, 2017 at 6:41 AM, Matthew Johnson <matthew@anandabits.com <mailto:matthew@anandabits.com>> wrote:
On Feb 10, 2017, at 9:48 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Wed, Feb 8, 2017 at 5:05 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

Our philosophy in general, however, is to default to the behavior which preserves the most flexibility for the library designer. Both open and non-open classes are common, but we chose to give non-open classes the `public` keyword because that's the flexibility-preserving option. Resilient enums are definitely the more flexible option, so by that rule, they should have plain `public`.

I think that, if enums were a brand-new feature being introduced for the first time in Swift 4, there would be no question that `public enum` ought to give you a resilient enum. The only good reason not to do that is source compatibility. So we need to decide: Which of these principles is more important to us?

1. "Defaults for public symbols should preserve as much flexibility for designers as possible."

2. "Swift 3 code should compile in Swift 4 without deprecation warnings, unmodified and without a version compatibility flag, whenever possible."

···

On Feb 14, 2017, at 6:16 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

So, perhaps I'm being simplistic here, but--

At the end of the day, aren't we simply trying to enable a resiliency feature? Could it not be said that an enum where future added cases aren't source-breaking is a more resilient enum?

Since there is consensus that the status quo is desirable for a lot of use cases, couldn't we keep spelling it "public enum" and just spell this proposed more resilient enum "@resilient public enum"?

--
Brent Royal-Gordon
Architechies

So, perhaps I'm being simplistic here, but--

At the end of the day, aren't we simply trying to enable a resiliency feature? Could it not be said that an enum where future added cases aren't source-breaking is a more resilient enum?

Since there is consensus that the status quo is desirable for a lot of use cases, couldn't we keep spelling it "public enum" and just spell this proposed more resilient enum "@resilient public enum”?

If it’s got to be done, this is the best solution, but to my mind, it shouldn’t be done at all.

There is no way that a library designer can possibly determine what constitutes “resilient” behaviour on the part of the client software. Maybe adding a default clause is the right thing, but maybe the default clause does something that it shouldn’t for some new enumeration cases. As the client software designer, my only defence is an exhaustive switch statement that relies on the compiler to tell me the it is no longer exhaustive. Yes, it means software in the field will crash until I distribute a new version but that is often preferable to other silent consequences. I really do not want to be forced to add a default to a switch that might reduce the safety of my software.

Adding new cases to any enumeration where the client software is meant to take different actions based on the different cases is source breaking. End of story. No point in trying to mark them as resilient.

Adding cases to enumerations that are meant to be passed in to the library e.g. an enumeration that specifies a compositing operation or a string encoding may well not be source breaking (how can you tell in every case?) but it’s unlikely that the client is going to be testing those enumerations in a switch statement anyway.

···

On 15 Feb 2017, at 02:16, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

On Tue, Feb 14, 2017 at 10:09 Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

> On Feb 14, 2017, at 3:43 AM, Brent Royal-Gordon <brent@architechies.com> wrote:
>
>> On Feb 13, 2017, at 7:45 AM, Matthew Johnson <matthew@anandabits.com> wrote:
>>
>> If you look closely, when most people say “closed enum” they mean a fixed, complete set of cases that are all public. But when people say “closed protocol” they don’t actually mean a fixed, complete set of conformances that are all public. They simply mean clients cannot add conformances. This is the semantic contract of resilient enums, not closed enums.
>
> Yes, our traditional terminology here has been a little bit confused.
>
>>> What I instead suggest is that we think of a closed enum as being like a fragile (non-resilient) struct. In both cases, you are committing to a particular design for the type. So I think we should give them both the same keyword—something like:
>>>
>>> @fixed struct Person {
>>> var name: String
>>> var birthDate: Date
>>> }
>>> @fixed enum Edge {
>>> case start
>>> case end
>>> }
>>>
>>
>> You omitted public here. Does that mean you intend for `@fixed` to imply public visibility? If so, I could get behind this. But I am curious why you made it an attribute rather than a keyword.
>
> No, I'm sorry, I meant to say `@fixed public struct` and `@fixed public enum`. I don't think `@fixed` implies public-ness, either, so it would need to be paired with a `public` keyword. There *may* be keywords we could use that would, like `exposed`

I agree that `fixed` (and `closed`) don’t imply `public` in terms of the colloquial meaning of the words and there is a reasonable case that `open` does. I’m not sure I like `exposed`, but maybe it’s possible to find a keyword that would more directly imply `public`.

> , but I'm not sure we want to make this feature so prominent,

I have some trouble getting on board with requiring an annotation *in addition* to `public` for the reasons I have already stated, and which led to `open` becoming an access modifier rather than an annotation. It’s possible I could be convinced otherwise, but I think it would require data showing that this really is a rare edge case. If the relatively frequency of closed vs resilient enums is reasonably similar to the relative frequency of public vs open enums I think there is a strong case to make them carry the same syntactic weight, as we did with `open`.

> and I'm not sure how that would work with classes you want to both expose and permit subclassing of. (Would that be `exposed open class Foo`?)

Can you elaborate on what you mean by "classes you want to both expose and permit subclassing of”? Do you mean commit to the set of fields being fixed like you indicated with a struct? If so, I’m not sure that is a valuable combination and my instinct is to ban it.

If we did want to support something like that it points to keeping `closed` (as in cases, subclasses and conformances) orthogonal to `fixed` (as in the set of stored properties).

>
>>> I don't see it mentioned here (maybe I just missed it), but even though we *could* do exhaustiveness checking on non-open protocols, I'm not convinced that's a good idea. Usually when you have several types conforming to a protocol, you should access type-specific behavior through polymorphism, not by switching on the protocol. A protocol is supposed to represent a behavior, not just mark a type in some arbitrary way.
>>
>> I agree that you should usually be adding polymorphism, but preventing exhaustive switch on what is effectively a style argument seems like an unnecessary restriction to me. There will be times when it could be used to good effect. I think the community has done a pretty good job of figuring out how to use Swift’s many features well and don’t believe it would be frequently abused.
>
> I agree we shouldn't change the language to *prevent* bad style. But this would go beyond that—we'd be putting specific engineering effort solely into *enabling* bad style. At minimum, this should fall so far down our to-do list that we'll probably never get to it.

This assumes that switching over conforming types is bad style. One of the biggest problems with switching over subclasses or conforming types is the fact that you don’t get compiler verification of exhaustiveness. If the language supports exhaustive switching for closed classes and protocols this becomes a non-issue.

I don’t know of any languages that support a kind of type which supports generic and dynamic dispatch as well as exhaustive switch. It may be interesting to have the ability to organize some methods by type (i.e. protocol requirements) and other methods by function (i.e. a protocol extension method with an exhaustive switch).

When you have exhaustive switch these are really just two different ways to organize code. Neither one is inherently better. Each has strengths different strengths. Why not allow the language to support both and let programmers decide which organization of their code is best in a particular case?

>
>>> I still support this general approach. One spelling could simply be `@nonopen`. Although if we don't use `closed`, we could simply use `@closed` like I suggested—here it really *would* be an antonym to `open`.
>>
>> I like the idea of using `@nonopen` for the transitional attribute. Both because it “removes the openness” that `public protocol` currently implies. In that sense it is probably the most accurate term we could find and it’s also pretty concise.
>
> It also sounds a little bit awkward, which is normally a reason not to use it, but perhaps that's actually a good thing in a temporary, transitional keyword.
>
>>>> A similar mult-release strategy would work for migrating public enums.
>>>
>>> What is it that needs migrating here? Lack of exhaustiveness checking? It sounds like we were planning to break that anyway in some fashion.
>>
>> Public enums are not currently resilient. Clients are allowed to switch over them without a `default` clause. This means that client code will fail to compile in a version of Swift where `public enum` has the resilient contract unless the library changes to adopt closed semantics or the client adds a default case.
>
> My thinking was that, since most existing `public` enums should probably not be `@fixed`, we should just change the behavior and let some switch statements break. Most `public` protocols, on the other hand, ought to become `open`, so we should flag that change and require an explicit marker like `@nonopen` if you really don't want to change over. But I could be convinced otherwise.

I think this hits on the basis of our disagreement. Is it really the case that *most* existing `public` enums should probably not be `@fixed`? Have you done an analysis to support this? Not just of the standard library, but also Apple’s frameworks and open source modules on Github? We might learn something interesting by doing an analysis like this. It certainly wouldn’t hurt.

If it turns out that closed / fixed is *often* (say 30-40% of the cases or more) the desirable contract using an annotation would result in noisy boilerplate.

On the other hand, if it turns out to be relatively rare (say 5-10%) it would become easier to forget the annotation, making an error of omission. Of course that error can be corrected pretty easily so maybe we it wouldn’t be a big deal if it really is rare.

>
> --
> Brent Royal-Gordon
> Architechies
>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

The Library Evolution document isn’t gospel; it’s more like a collection of musings and aspirations, so I don’t think we should be bound by the syntax it uses.

Agreed on this very local point. :-) There are some deliberately ugly names in there so that we don't end up using that syntax.

Jordan

Not quite. Resilience is about making changes that aren't source-breaking also not ABI-breaking (within reason, balanced against performance implications). "Open" vs. "non-open" enums also affect modules distributed as source—is it a source-breaking change to add a new case or not?

Jordan

···

On Feb 14, 2017, at 18:16, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

So, perhaps I'm being simplistic here, but--

At the end of the day, aren't we simply trying to enable a resiliency feature? Could it not be said that an enum where future added cases aren't source-breaking is a more resilient enum?

Since there is consensus that the status quo is desirable for a lot of use cases, couldn't we keep spelling it "public enum" and just spell this proposed more resilient enum "@resilient public enum"?

So, perhaps I'm being simplistic here, but--

At the end of the day, aren't we simply trying to enable a resiliency feature? Could it not be said that an enum where future added cases aren't source-breaking is a more resilient enum?

Since there is consensus that the status quo is desirable for a lot of use cases, couldn't we keep spelling it "public enum" and just spell this proposed more resilient enum "@resilient public enum"?

Our philosophy in general, however, is to default to the behavior which preserves the most flexibility for the library designer. Both open and non-open classes are common, but we chose to give non-open classes the `public` keyword because that's the flexibility-preserving option. Resilient enums are definitely the more flexible option, so by that rule, they should have plain `public`.

I think that, if enums were a brand-new feature being introduced for the first time in Swift 4, there would be no question that `public enum` ought to give you a resilient enum. The only good reason not to do that is source compatibility. So we need to decide: Which of these principles is more important to us?

1. "Defaults for public symbols should preserve as much flexibility for designers as possible."

2. "Swift 3 code should compile in Swift 4 without deprecation warnings, unmodified and without a version compatibility flag, whenever possible.”

It’s also worth re-iterating the point I have made about inconsistent semantics for `public`, and that this problem as well as your first point also impacts protocols. That means we have two reasons to make a change and both impact protocols and enums. We also have a very reasonable way to introduce the changes necessary to solve these problems.

I think the changes are worth making, and certainly worthy of at least bringing a proposal to review.

···

On Feb 15, 2017, at 5:11 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Feb 14, 2017, at 6:16 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

--
Brent Royal-Gordon
Architechies

So, perhaps I'm being simplistic here, but--

At the end of the day, aren't we simply trying to enable a resiliency feature? Could it not be said that an enum where future added cases aren't source-breaking is a more resilient enum?

Since there is consensus that the status quo is desirable for a lot of use cases, couldn't we keep spelling it "public enum" and just spell this proposed more resilient enum "@resilient public enum”?

If it’s got to be done, this is the best solution, but to my mind, it shouldn’t be done at all.

There is no way that a library designer can possibly determine what constitutes “resilient” behaviour on the part of the client software. Maybe adding a default clause is the right thing, but maybe the default clause does something that it shouldn’t for some new enumeration cases. As the client software designer, my only defence is an exhaustive switch statement that relies on the compiler to tell me the it is no longer exhaustive. Yes, it means software in the field will crash until I distribute a new version but that is often preferable to other silent consequences. I really do not want to be forced to add a default to a switch that might reduce the safety of my software.

Adding new cases to any enumeration where the client software is meant to take different actions based on the different cases is source breaking. End of story. No point in trying to mark them as resilient.

Adding cases to enumerations that are meant to be passed in to the library e.g. an enumeration that specifies a compositing operation or a string encoding may well not be source breaking (how can you tell in every case?) but it’s unlikely that the client is going to be testing those enumerations in a switch statement anyway.

In order to support adding cases to enums meant to be passed in to a library we have to introduce some kind of syntax to distinguish these enums from enums that are not resilient. We’ve been discussing options for how to do this.

···

On Feb 15, 2017, at 5:52 AM, Jeremy Pereira <jeremy.j.pereira@googlemail.com> wrote:

On 15 Feb 2017, at 02:16, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

On Tue, Feb 14, 2017 at 10:09 Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 14, 2017, at 3:43 AM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Feb 13, 2017, at 7:45 AM, Matthew Johnson <matthew@anandabits.com> wrote:

If you look closely, when most people say “closed enum” they mean a fixed, complete set of cases that are all public. But when people say “closed protocol” they don’t actually mean a fixed, complete set of conformances that are all public. They simply mean clients cannot add conformances. This is the semantic contract of resilient enums, not closed enums.

Yes, our traditional terminology here has been a little bit confused.

What I instead suggest is that we think of a closed enum as being like a fragile (non-resilient) struct. In both cases, you are committing to a particular design for the type. So I think we should give them both the same keyword—something like:

   @fixed struct Person {
           var name: String
           var birthDate: Date
   }
   @fixed enum Edge {
           case start
           case end
   }

You omitted public here. Does that mean you intend for `@fixed` to imply public visibility? If so, I could get behind this. But I am curious why you made it an attribute rather than a keyword.

No, I'm sorry, I meant to say `@fixed public struct` and `@fixed public enum`. I don't think `@fixed` implies public-ness, either, so it would need to be paired with a `public` keyword. There *may* be keywords we could use that would, like `exposed`

I agree that `fixed` (and `closed`) don’t imply `public` in terms of the colloquial meaning of the words and there is a reasonable case that `open` does. I’m not sure I like `exposed`, but maybe it’s possible to find a keyword that would more directly imply `public`.

, but I'm not sure we want to make this feature so prominent,

I have some trouble getting on board with requiring an annotation *in addition* to `public` for the reasons I have already stated, and which led to `open` becoming an access modifier rather than an annotation. It’s possible I could be convinced otherwise, but I think it would require data showing that this really is a rare edge case. If the relatively frequency of closed vs resilient enums is reasonably similar to the relative frequency of public vs open enums I think there is a strong case to make them carry the same syntactic weight, as we did with `open`.

and I'm not sure how that would work with classes you want to both expose and permit subclassing of. (Would that be `exposed open class Foo`?)

Can you elaborate on what you mean by "classes you want to both expose and permit subclassing of”? Do you mean commit to the set of fields being fixed like you indicated with a struct? If so, I’m not sure that is a valuable combination and my instinct is to ban it.

If we did want to support something like that it points to keeping `closed` (as in cases, subclasses and conformances) orthogonal to `fixed` (as in the set of stored properties).

I don't see it mentioned here (maybe I just missed it), but even though we *could* do exhaustiveness checking on non-open protocols, I'm not convinced that's a good idea. Usually when you have several types conforming to a protocol, you should access type-specific behavior through polymorphism, not by switching on the protocol. A protocol is supposed to represent a behavior, not just mark a type in some arbitrary way.

I agree that you should usually be adding polymorphism, but preventing exhaustive switch on what is effectively a style argument seems like an unnecessary restriction to me. There will be times when it could be used to good effect. I think the community has done a pretty good job of figuring out how to use Swift’s many features well and don’t believe it would be frequently abused.

I agree we shouldn't change the language to *prevent* bad style. But this would go beyond that—we'd be putting specific engineering effort solely into *enabling* bad style. At minimum, this should fall so far down our to-do list that we'll probably never get to it.

This assumes that switching over conforming types is bad style. One of the biggest problems with switching over subclasses or conforming types is the fact that you don’t get compiler verification of exhaustiveness. If the language supports exhaustive switching for closed classes and protocols this becomes a non-issue.

I don’t know of any languages that support a kind of type which supports generic and dynamic dispatch as well as exhaustive switch. It may be interesting to have the ability to organize some methods by type (i.e. protocol requirements) and other methods by function (i.e. a protocol extension method with an exhaustive switch).

When you have exhaustive switch these are really just two different ways to organize code. Neither one is inherently better. Each has strengths different strengths. Why not allow the language to support both and let programmers decide which organization of their code is best in a particular case?

I still support this general approach. One spelling could simply be `@nonopen`. Although if we don't use `closed`, we could simply use `@closed` like I suggested—here it really *would* be an antonym to `open`.

I like the idea of using `@nonopen` for the transitional attribute. Both because it “removes the openness” that `public protocol` currently implies. In that sense it is probably the most accurate term we could find and it’s also pretty concise.

It also sounds a little bit awkward, which is normally a reason not to use it, but perhaps that's actually a good thing in a temporary, transitional keyword.

A similar mult-release strategy would work for migrating public enums.

What is it that needs migrating here? Lack of exhaustiveness checking? It sounds like we were planning to break that anyway in some fashion.

Public enums are not currently resilient. Clients are allowed to switch over them without a `default` clause. This means that client code will fail to compile in a version of Swift where `public enum` has the resilient contract unless the library changes to adopt closed semantics or the client adds a default case.

My thinking was that, since most existing `public` enums should probably not be `@fixed`, we should just change the behavior and let some switch statements break. Most `public` protocols, on the other hand, ought to become `open`, so we should flag that change and require an explicit marker like `@nonopen` if you really don't want to change over. But I could be convinced otherwise.

I think this hits on the basis of our disagreement. Is it really the case that *most* existing `public` enums should probably not be `@fixed`? Have you done an analysis to support this? Not just of the standard library, but also Apple’s frameworks and open source modules on Github? We might learn something interesting by doing an analysis like this. It certainly wouldn’t hurt.

If it turns out that closed / fixed is *often* (say 30-40% of the cases or more) the desirable contract using an annotation would result in noisy boilerplate.

On the other hand, if it turns out to be relatively rare (say 5-10%) it would become easier to forget the annotation, making an error of omission. Of course that error can be corrected pretty easily so maybe we it wouldn’t be a big deal if it really is rare.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Our philosophy in general, however, is to default to the behavior which preserves the most flexibility for the library designer.

Actually, I thought the philosophy was to preserver type safety. When did that change?

Also, when was the library designer prioritised ahead of the application developer?

Both open and non-open classes are common, but we chose to give non-open classes the `public` keyword because that's the flexibility-preserving option.

No it isn’t, it’s the flexibility restricting option. The consumer of an open class can subclass it. The consumer of a public class cannot subclass it. How is the second more flexible than the first?

···

On 15 Feb 2017, at 11:11, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Our philosophy in general, however, is to default to the behavior which preserves the most flexibility for the library designer.

Actually, I thought the philosophy was to preserver type safety. When did that change?

Also, when was the library designer prioritised ahead of the application developer?

Both open and non-open classes are common, but we chose to give non-open classes the `public` keyword because that's the flexibility-preserving option.

No it isn’t, it’s the flexibility restricting option. The consumer of an open class can subclass it. The consumer of a public class cannot subclass it. How is the second more flexible than the first?

It reduces complexity for the library author by allowing them to opt-out of the complexity involved in supporting unknown, user-defined subclasses. It is important to allow libraries to have this flexibility. They are free to declare a class `open` if they want to allow subclassing. It’s even possibly for a library to declare all classes `open` if it wishes to do so. But *requiring* that would reduce the design space libraries are allowed to explore and / or introduce fragility by moving the subclass restriction to a comment.

···

On Feb 15, 2017, at 5:59 AM, Jeremy Pereira via swift-evolution <swift-evolution@swift.org> wrote:

On 15 Feb 2017, at 11:11, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Our philosophy in general, however, is to default to the behavior which preserves the most flexibility for the library designer.

Actually, I thought the philosophy was to preserver type safety. When did that change?

Also, when was the library designer prioritised ahead of the application developer?

Both open and non-open classes are common, but we chose to give non-open classes the `public` keyword because that's the flexibility-preserving option.

No it isn’t, it’s the flexibility restricting option. The consumer of an open class can subclass it. The consumer of a public class cannot subclass it. How is the second more flexible than the first?

It reduces complexity for the library author by allowing them to opt-out of the complexity involved in supporting unknown, user-defined subclasses. It is important to allow libraries to have this flexibility. They are free to declare a class `open` if they want to allow subclassing. It’s even possibly for a library to declare all classes `open` if it wishes to do so. But *requiring* that would reduce the design space libraries are allowed to explore and / or introduce fragility by moving the subclass restriction to a comment.

Why would a library author want to prohibit subclasses?
A library user can always wrap the class and subclass the wrapper.

There are cases where subclassing does not make sense. And thus preventing subclasses adds information for those users that don’t RTFM. But that imo is not worth the impact extra complexity places on all other users.

Rien.

···

On 15 Feb 2017, at 16:11, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 15, 2017, at 5:59 AM, Jeremy Pereira via swift-evolution <swift-evolution@swift.org> wrote:

On 15 Feb 2017, at 11:11, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Our philosophy in general, however, is to default to the behavior which preserves the most flexibility for the library designer.

Actually, I thought the philosophy was to preserver type safety. When did that change?

Also, when was the library designer prioritised ahead of the application developer?

Both open and non-open classes are common, but we chose to give non-open classes the `public` keyword because that's the flexibility-preserving option.

No it isn’t, it’s the flexibility restricting option. The consumer of an open class can subclass it. The consumer of a public class cannot subclass it. How is the second more flexible than the first?

It reduces complexity for the library author by allowing them to opt-out of the complexity involved in supporting unknown, user-defined subclasses. It is important to allow libraries to have this flexibility. They are free to declare a class `open` if they want to allow subclassing. It’s even possibly for a library to declare all classes `open` if it wishes to do so. But *requiring* that would reduce the design space libraries are allowed to explore and / or introduce fragility by moving the subclass restriction to a comment.

Why would a library author want to prohibit subclasses?
A library user can always wrap the class and subclass the wrapper.

This is composition, not inheritance. The most important difference is that a wrapper cannot override methods, it can only wrap and / or forward them. This means that when the superclass calls a method on `self` that method *always* invokes its version of that method rather than a subclass override. This is a very important difference.

···

On Feb 15, 2017, at 9:35 AM, Rien <Rien@Balancingrock.nl> wrote:

On 15 Feb 2017, at 16:11, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 15, 2017, at 5:59 AM, Jeremy Pereira via swift-evolution <swift-evolution@swift.org> wrote:

On 15 Feb 2017, at 11:11, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

There are cases where subclassing does not make sense. And thus preventing subclasses adds information for those users that don’t RTFM. But that imo is not worth the impact extra complexity places on all other users.

Rien.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Our philosophy in general, however, is to default to the behavior which preserves the most flexibility for the library designer.

Actually, I thought the philosophy was to preserver type safety. When did that change?

Also, when was the library designer prioritised ahead of the application developer?

Both open and non-open classes are common, but we chose to give non-open classes the `public` keyword because that's the flexibility-preserving option.

No it isn’t, it’s the flexibility restricting option. The consumer of an open class can subclass it. The consumer of a public class cannot subclass it. How is the second more flexible than the first?

It reduces complexity for the library author by allowing them to opt-out of the complexity involved in supporting unknown, user-defined subclasses. It is important to allow libraries to have this flexibility. They are free to declare a class `open` if they want to allow subclassing. It’s even possibly for a library to declare all classes `open` if it wishes to do so. But *requiring* that would reduce the design space libraries are allowed to explore and / or introduce fragility by moving the subclass restriction to a comment.

Why would a library author want to prohibit subclasses?
A library user can always wrap the class and subclass the wrapper.

This is composition, not inheritance. The most important difference is that a wrapper cannot override methods, it can only wrap and / or forward them. This means that when the superclass calls a method on `self` that method *always* invokes its version of that method rather than a subclass override. This is a very important difference.

Agreed, however that does not answer the question why would a library developer want to disallow subclassing?
I do not see a use case for that. I.e. a feature that cannot be implemented without it. (without “open”)

Rien.

···

On 15 Feb 2017, at 16:45, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 15, 2017, at 9:35 AM, Rien <Rien@Balancingrock.nl> wrote:

On 15 Feb 2017, at 16:11, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 15, 2017, at 5:59 AM, Jeremy Pereira via swift-evolution <swift-evolution@swift.org> wrote:

On 15 Feb 2017, at 11:11, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

There are cases where subclassing does not make sense. And thus preventing subclasses adds information for those users that don’t RTFM. But that imo is not worth the impact extra complexity places on all other users.

Rien.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Our philosophy in general, however, is to default to the behavior which preserves the most flexibility for the library designer.

Actually, I thought the philosophy was to preserver type safety. When did that change?

Also, when was the library designer prioritised ahead of the application developer?

Both open and non-open classes are common, but we chose to give non-open classes the `public` keyword because that's the flexibility-preserving option.

No it isn’t, it’s the flexibility restricting option. The consumer of an open class can subclass it. The consumer of a public class cannot subclass it. How is the second more flexible than the first?

It reduces complexity for the library author by allowing them to opt-out of the complexity involved in supporting unknown, user-defined subclasses. It is important to allow libraries to have this flexibility. They are free to declare a class `open` if they want to allow subclassing. It’s even possibly for a library to declare all classes `open` if it wishes to do so. But *requiring* that would reduce the design space libraries are allowed to explore and / or introduce fragility by moving the subclass restriction to a comment.

Why would a library author want to prohibit subclasses?
A library user can always wrap the class and subclass the wrapper.

This is composition, not inheritance. The most important difference is that a wrapper cannot override methods, it can only wrap and / or forward them. This means that when the superclass calls a method on `self` that method *always* invokes its version of that method rather than a subclass override. This is a very important difference.

Agreed, however that does not answer the question why would a library developer want to disallow subclassing?
I do not see a use case for that. I.e. a feature that cannot be implemented without it. (without “open”)

The feature it enables is more robust libraries and the ability for library authors to better reason about their code. You may not find this benefit enough to be worth a language feature, but many of us do.

···

On Feb 15, 2017, at 9:59 AM, Rien <Rien@Balancingrock.nl> wrote:

On 15 Feb 2017, at 16:45, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 15, 2017, at 9:35 AM, Rien <Rien@Balancingrock.nl> wrote:

On 15 Feb 2017, at 16:11, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 15, 2017, at 5:59 AM, Jeremy Pereira via swift-evolution <swift-evolution@swift.org> wrote:

On 15 Feb 2017, at 11:11, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Rien.

There are cases where subclassing does not make sense. And thus preventing subclasses adds information for those users that don’t RTFM. But that imo is not worth the impact extra complexity places on all other users.

Rien.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

A short example where I personally wanted a public-but-not-open protocol:

public protocol SubscriptParameterType {
     
    // This property was needed to prevent the client from breaking
    // the library by conforming to the protocol, but I'd like to
    // keep it invisible for the client, or even better prevent the
    // client from conforming to the protocol.
    var parameter: Document.SubscriptParameter { get }
}

extension Document {
     
    public enum SubscriptParameter {
             
        case string(String)
        case integer(Int)
    }
}

extension String : SubscriptParameterType {
     
    public var parameter: Document.SubscriptParameter {
         
        return .string(self)
    }
}

extension Int : SubscriptParameterType {
     
    public var parameter: Document.SubscriptParameter {
         
        return .integer(self)
    }
}

// Somewhere inside the `Document` type
public subscript(firstKey: String, parameters: SubscriptParameterType...) -> Value? { … }
That implementation enables more safe queries of my Document type like document["key1", intIndexInstance, stringKeyInstance, 10, "key"] rather than document["key1/\(intIndexInstance)/\(stringKeyInstance)/10/key"].

···

--
Adrian Zubarev
Sent with Airmail

Am 15. Februar 2017 um 17:03:32, Matthew Johnson via swift-evolution (swift-evolution@swift.org) schrieb:

On Feb 15, 2017, at 9:59 AM, Rien <Rien@Balancingrock.nl> wrote:

On 15 Feb 2017, at 16:45, Matthew Johnson <matthew@anandabits.com> wrote:

On Feb 15, 2017, at 9:35 AM, Rien <Rien@Balancingrock.nl> wrote:

On 15 Feb 2017, at 16:11, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 15, 2017, at 5:59 AM, Jeremy Pereira via swift-evolution <swift-evolution@swift.org> wrote:

On 15 Feb 2017, at 11:11, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Our philosophy in general, however, is to default to the behavior which preserves the most flexibility for the library designer.

Actually, I thought the philosophy was to preserver type safety. When did that change?

Also, when was the library designer prioritised ahead of the application developer?

Both open and non-open classes are common, but we chose to give non-open classes the `public` keyword because that's the flexibility-preserving option.

No it isn’t, it’s the flexibility restricting option. The consumer of an open class can subclass it. The consumer of a public class cannot subclass it. How is the second more flexible than the first?

It reduces complexity for the library author by allowing them to opt-out of the complexity involved in supporting unknown, user-defined subclasses. It is important to allow libraries to have this flexibility. They are free to declare a class `open` if they want to allow subclassing. It’s even possibly for a library to declare all classes `open` if it wishes to do so. But *requiring* that would reduce the design space libraries are allowed to explore and / or introduce fragility by moving the subclass restriction to a comment.

Why would a library author want to prohibit subclasses?
A library user can always wrap the class and subclass the wrapper.

This is composition, not inheritance. The most important difference is that a wrapper cannot override methods, it can only wrap and / or forward them. This means that when the superclass calls a method on `self` that method *always* invokes its version of that method rather than a subclass override. This is a very important difference.

Agreed, however that does not answer the question why would a library developer want to disallow subclassing?
I do not see a use case for that. I.e. a feature that cannot be implemented without it. (without “open”)

The feature it enables is more robust libraries and the ability for library authors to better reason about their code. You may not find this benefit enough to be worth a language feature, but many of us do.

Rien.

There are cases where subclassing does not make sense. And thus preventing subclasses adds information for those users that don’t RTFM. But that imo is not worth the impact extra complexity places on all other users.

Rien.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution