<html><head><style>
body {
        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
        padding:1em;
        margin:auto;
        background:#fefefe;
}
h1, h2, h3, h4, h5, h6 {
        font-weight: bold;
}
h1 {
        color: #000000;
        font-size: 28pt;
}
h2 {
        border-bottom: 1px solid #CCCCCC;
        color: #000000;
        font-size: 24px;
}
h3 {
        font-size: 18px;
}
h4 {
        font-size: 16px;
}
h5 {
        font-size: 14px;
}
h6 {
        color: #777777;
        background-color: inherit;
        font-size: 14px;
}
hr {
        height: 0.2em;
        border: 0;
        color: #CCCCCC;
        background-color: #CCCCCC;
display: inherit;
}
p, blockquote, ul, ol, dl, li, table, pre {
        margin: 15px 0;
}
a, a:visited {
        color: #4183C4;
        background-color: inherit;
        text-decoration: none;
}
#message {
        border-radius: 6px;
        border: 1px solid #ccc;
        display:block;
        width:100%;
        height:60px;
        margin:6px 0px;
}
button, #ws {
        font-size: 12 pt;
        padding: 4px 6px;
        border-radius: 5px;
        border: 1px solid #bbb;
        background-color: #eee;
}
code, pre, #ws, #message {
        font-family: Monaco;
        font-size: 10pt;
        border-radius: 3px;
        background-color: #F8F8F8;
        color: inherit;
}
code {
        border: 1px solid #EAEAEA;
        margin: 0 2px;
        padding: 0 5px;
}
pre {
        border: 1px solid #CCCCCC;
        overflow: auto;
        padding: 4px 8px;
}
pre > code {
        border: 0;
        margin: 0;
        padding: 0;
}
#ws { background-color: #f8f8f8; }
.bloop_markdown table {
border-collapse: collapse;
font-family: Helvetica, arial, freesans, clean, sans-serif;
color: rgb(51, 51, 51);
font-size: 15px; line-height: 25px;
padding: 0; }
.bloop_markdown table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }
.bloop_markdown table tr:nth-child(2n) {
background-color: #f8f8f8; }
.bloop_markdown table tr th {
font-weight: bold;
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
.bloop_markdown table tr td {
border: 1px solid #cccccc;
margin: 0;
padding: 6px 13px; }
.bloop_markdown table tr th :first-child, table tr td :first-child {
margin-top: 0; }
.bloop_markdown table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }
.bloop_markdown blockquote{
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777; }
blockquote > :first-child {
margin-top: 0; }
blockquote > :last-child {
margin-bottom: 0; }
code, pre, #ws, #message {
word-break: normal;
word-wrap: normal;
}
hr {
display: inherit;
}
.bloop_markdown :first-child {
-webkit-margin-before: 0;
}
code, pre, #ws, #message {
font-family: Menlo, Consolas, Liberation Mono, Courier, monospace;
}
.send { color:#77bb77; }
.server { color:#7799bb; }
.error { color:#AA0000; }</style></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;"><div class="bloop_markdown"><p>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 <code>open enum</code> vs. <code>public enum</code> and <code>closed enum</code>.</p>
<p><code>public</code> today does not add any guarantee to prevent the client from extending your type. For instance:</p>
<pre><code class="swift">// Module A
public class A { public init() {} }
// Module B
extension A {
convenience init(foo: Int) {
print(foo)
self.init()
}
}
</code></pre>
<p>That also implies to me that <code>open</code> as an access modifier does not prevent extensibility.</p>
<p>Speaking of opened enums, we really do not mean <code>open enum</code> to allow extensibility where <code>closed enum</code> would mean the opposite. <code>closed</code> or <code>@closed</code> by all the definitions I’ve read so far is what the current <code>public</code> means for enums. If this is going to be fixed to <code>closed enum</code> (<code>@closed public enum</code>) than what we’re currently speaking of is nothing else than <code>public enum</code>, which would be extensible if that future might be added to swift one day.</p>
<p>Again, I see <code>open</code> only as a contract to prevent sub-typing, conformances and overriding, where extensibility of a type a story of it’s own.</p>
<p>Quickly compared to protocols: <code>public-but-not-open protocol</code> from module A should remain extensible in module B. Consistently that would mean that <code>public enum</code> 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 <code>closed</code>. <code>open enum</code> on the other hand makes only sense when we’d speak about sub-typing on enums or value types in general.</p>
<p></p></div><div class="bloop_original_html"><style>body{font-family:Helvetica,Arial;font-size:13px}</style><div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;"><br></div> <br> <div id="bloop_sign_1486822474406961920" class="bloop_sign"><div style="font-family:helvetica,arial;font-size:13px">-- <br>Adrian Zubarev<br>Sent with Airmail</div></div> <br><p class="airmail_on">Am 11. Februar 2017 um 14:08:02, Matthew Johnson (<a href="mailto:matthew@anandabits.com">matthew@anandabits.com</a>) schrieb:</p> <blockquote type="cite" class="clean_bq"><span><div dir="auto"><div></div><div>
<title></title>
<div><br>
<br>
Sent from my iPad</div>
<div><br>
On Feb 11, 2017, at 4:25 AM, Adrian Zubarev via swift-evolution
<<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>>
wrote:<br>
<br></div>
<blockquote type="cite">
<div>
<div class="bloop_markdown">
<p>I’m probably better describing things with some bikeshedding
code, but feel free to criticize it as much as you’d like.</p>
<pre><code class="swift">//===========--------- Module A ---------===========//
@closed public enum A {
case a
}
extension A {
case aa // error, because enum is closed
}
</code></pre></div>
</div>
</blockquote>
<div>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`.</div>
<div><br></div>
<br>
<blockquote type="cite">
<div>
<div class="bloop_markdown">
<pre><code class="swift">
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
}</code></pre></div>
</div>
</blockquote>
<div>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).</div>
<br>
<blockquote type="cite">
<div>
<div class="bloop_markdown">
<pre><code class="swift">
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
}
</code></pre></div>
</div>
</blockquote>
<div>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.</div>
<div><br></div>
<div>All values of A are valid values of SubA, but SubA has values
that are not valid values of A.</div>
<br>
<blockquote type="cite">
<div>
<div class="bloop_markdown">
<pre><code class="swift">// The following enum can have a sub-enum in the clients module
open enum C {
case c
}</code></pre></div>
</div>
</blockquote>
<blockquote type="cite">
<div>
<div class="bloop_markdown">
<pre><code class="swift">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 = <a href="http://SubC.cc">SubC.cc</a>
cool(c: subC) // okay
enum SubD : D {
case dd
}
doo(d: D.dd)// error, downgrade sub-enum to D first
</code></pre>
<p>My point here is, that we should not think of (possible)
<code>open enums</code> as enums that the client is allowed to
extend. That way we’re only creating another inconsistent case for
the <code>open</code> access modifier. As far as I can tell,
<code>open</code> as for today means “the client is allowed to
subclass/override things from a different module”.</p>
</div>
</div>
</blockquote>
<div>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.</div>
<div><br></div>
<blockquote type="cite">
<div>
<div class="bloop_markdown">
<p>And I already said it hundred of times that we should extend
this to make <code>open</code> a true <em>access modifier</em> in
Swift. That said the meaning of <code>open</code> should
become:</p>
<ul>
<li>The client is allowed to sub-type (currently only classes are
supported).</li>
<li>The client is allowed to conform to open protocols</li>
<li>The client is allowed to override open type members</li>
</ul>
<p>This also means that extensibility is still allowed to
<code>public</code> types. Public-but-not-open classes are still
extensible today, which is the correct behavior. Extending an enum
which is not closed <em>could</em> or probably should be made
possible through extensions, because <em>I</em> cannot think of
anther elegant way for the client to do so.</p>
</div>
</div>
</blockquote>
<div>This is what `open enum` would allow. It is the proper
enum analogue of open classes.</div>
<div><br></div>
<blockquote type="cite">
<div>
<div class="bloop_markdown">
<p>That will leave us the possibility to think of sub-typing enums
in the future (I sketched it out a little above).</p>
</div>
</div>
</blockquote>
<div>Value subtyping is very interesting. I have been working
on some ideas around this but I want to keep this thread
focused.</div>
<br>
<blockquote type="cite">
<div>
<div class="bloop_markdown">
<p>If I’m not mistaken, every enum case is known at compile
time,</p>
</div>
</div>
</blockquote>
<div>This is true today but will not always be true in the future.
That is in large part what this thread is about.</div>
<br>
<blockquote type="cite">
<div>
<div class="bloop_markdown">
<p>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
<code>open</code> access modifier does and how <code>@closed</code>
behaves.</p>
</div>
</div>
</blockquote>
<div>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.</div>
<br>
<blockquote type="cite">
<div>
<div class="bloop_markdown">
<p>To summarize:</p>
<ul>
<li><code>@closed enum</code> - you’re not allowed to add new cases
to the enum in your lib + (you’re allowed to create sub-enums)</li>
<li><code>@closed public enum</code> - you and the client are not
allowed to add new cases (+ the client is not allowed to create
sub-enums)</li>
<li><code>@closed open enum</code> - you and the client are not
allowed to add new cases (+ the client might create new
sub-enums)</li>
<li><code>enum</code> - you’re allowed to add new cases
(<code>default</code> is needed in switch statements) (+ you can
create new sub-enums)</li>
<li><code>public enum</code> - you and the client are allowed to
add new cases (+ only you are allowed to create new sub-enums)</li>
<li><code>open enum</code> - you and the client are allowed to add
new cases (everyone can create new sub-enums)</li>
</ul>
<p>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>
<p>P.S.: If we had something like this:</p>
<pre><code class="swift">@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()
}
}
}
</code></pre></div>
<div class="bloop_original_html">
<div id="bloop_customfont" style="font-family:Helvetica,Arial;font-size:13px; color: rgba(0,0,0,1.0); margin: 0px; line-height: auto;">
<br></div>
<br>
<div id="bloop_sign_1486806388447838976" class="bloop_sign">
<div style="font-family:helvetica,arial;font-size:13px">
-- <br>
Adrian Zubarev<br>
Sent with Airmail</div>
</div>
<br>
<p class="airmail_on">Am 11. Februar 2017 um 04:49:11, Xiaodi Wu
via swift-evolution (<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a>)
schrieb:</p>
<blockquote type="cite" class="clean_bq">
<div>
<div>
<div dir="ltr"><span>On Wed, Feb 8, 2017 at 5:05 PM, Matthew
Johnson via swift-evolution <span dir="ltr"><<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>></span>
wrote:<br></span>
<div class="gmail_extra">
<div class="gmail_quote">
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div style="word-wrap:break-word">
<div>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.</div>
<div><br></div>
<div><br></div>
<div>Background and Motivation:</div>
<div><br></div>
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).
<div><br></div>
<div>A recent thread (<a href="https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170206/031566.html" target="_blank">https://lists.swift.org/piper<wbr>mail/swift-evolution/Week-of-<wbr>Mon-20170206/031566.html</a>)
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 (<a href="https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums" target="_blank">https://github.com/apple/swif<wbr>t/blob/master/docs/LibraryEvol<wbr>ution.rst#enums</a>)
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.</div>
<div><br></div>
<div>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.</div>
<div><br></div>
<div>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.</div>
<div><br></div>
<div>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.</div>
<div><br></div>
<div>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.</div>
<div><br></div>
<div>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.</div>
<div><br></div>
<div><br></div>
<div>Proposal:</div>
<div><br></div>
<div>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.</div>
<div><br></div>
<div>`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)</div>
<div><br></div>
<div>`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.</div>
<div><br></div>
<div>`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.</div>
<div><br></div>
<div>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`.</div>
<div><br></div>
<div>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.</div>
<div><br></div>
<div><br></div>
<div>Source compatibility:</div>
<div><br></div>
<div>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.</div>
<div><br></div>
<div>Brent Royal-Gordon suggested such a strategy in a discussion
regarding closed protocols on Twitter:</div>
<div><br></div>
<div>* In Swift 4: all unannotated public protocols receive a
warning, possibly with a fix-it to change the annotation to
`open`.</div>
<div>* 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.</div>
<div>* Also In Swift 4: the `closed` modifier is introduced.</div>
<div><br></div>
<div>* In Swift 5 the warning becomes a compiler error.
`public protocol` is not allowed. Users must use
`@annotation public protocol`.</div>
<div>* 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.</div>
<div>* In Swift 7 `@annotation public protocol` is no longer
allowed.</div>
<div><br></div>
<div>A similar mult-release strategy would work for migrating
public enums.</div>
</div>
</blockquote>
<div><br></div>
<div>A different line of feedback here:</div>
<div><br></div>
<div>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.</div>
<div><br></div>
<div>- I'll leave aside closed protocols, which as per Jordan
Rose's feedback may or may not have sufficient
interestingness.</div>
<div>- 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:</div>
<div><br></div>
<div>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`).<br></div>
<div>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`.</div>
<div>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.</div>
<div><br></div>
</div>
</div>
</div>
_______________________________________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br>
</div>
</div>
</blockquote>
</div>
<div class="bloop_markdown"></div>
</div>
</blockquote>
<blockquote type="cite">
<div>
<span>_______________________________________________</span><br>
<span>swift-evolution mailing list</span><br>
<span><a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a></span><br>
<span><a href="https://lists.swift.org/mailman/listinfo/swift-evolution">https://lists.swift.org/mailman/listinfo/swift-evolution</a></span><br>
</div>
</blockquote>
</div></div></span></blockquote></div><div class="bloop_markdown"><p></p></div></body></html>