<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><br class=""><div><blockquote type="cite" class=""><div class="">On Feb 20, 2017, at 2:38 PM, Joe Groff <<a href="mailto:jgroff@apple.com" class="">jgroff@apple.com</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><blockquote type="cite" class=""><div class=""><br class="Apple-interchange-newline">On Feb 20, 2017, at 7:32 AM, Matthew Johnson via swift-evolution <<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div class="" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;"><blockquote type="cite" class=""><div class=""><br class="Apple-interchange-newline">On Feb 20, 2017, at 12:40 AM, Niels Andriesse via swift-evolution <<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div dir="ltr" class=""><div class="">I'd like to discuss the possibility of treating the cases of a given enum as if they are subtypes of that enum. This seems like a natural thing to do because enum cases (especially when they have associated values) effectively define a closed set of subtypes.</div><div class=""><br class=""></div><div class="">Doing so would allow for constructions such as the following:</div><div class=""><br class=""></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class="">enum Foo {</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""><span class="" style="white-space: pre;"> </span>case a(name: String)</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class="">}</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""><br class=""></font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class="">func isA(foo: Foo) -> Bool {</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> // The old way:</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> if case .a = foo { return true }</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> return false</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> // The new way:</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> return foo is .a</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class="">}</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""><br class=""></font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class="">func printNameIfFooIsA(foo: Foo) -> Bool {</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> // The old way:</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> if case let .a(name) = foo {</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> print(name)</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> }</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> // The new way (1):</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> if let a = foo as? .a {</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> print(<a href="http://a.name/" class="">a.name</a>)</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> }</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> // The new way (2):</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> if let name = (foo as? .a)?.name {</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> print(name)</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class=""> }</font></span></div><div class=""><span class="" style="background-color: rgb(255, 255, 255);"><font face="monospace, monospace" class="">}</font></span></div><div class=""><br class=""></div><div class="">Treating an enum's cases as its subtypes would make enums easier to work with because handling them would be syntactically the same as handling other types.</div><div class=""><br class=""></div><div class="">The pattern matching capabilities of enums wouldn't be affected by this proposal.</div><div class=""><br class=""></div><div class="">Multiple other proposals have already attempted to simplify enum handling (they have particularly focused on getting rid of "if case" and adding the ability to treat enum case tests as expressions), but none of the solutions presented in those proposals have worked out so far.</div><div class=""><br class=""></div><div class="">I believe that this could be the right solution to multiple enum-related problems that have been brought up repeatedly.</div></div></div></blockquote><div class=""><br class=""></div><div class="">I would like to see enum cases treated as subtypes of the enum type. This is an interesting way to refer to the type of a case. Unfortunately I don’t think it will work if we accept the proposal to give cases a compound name. If we do that the name of this case becomes `a(name:)` which is not a valid type name.</div></div></div></blockquote><br class=""></div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">I think there are definitely places where having cases be a subtype of an enum make sense, but I don't think it makes sense for *all* cases to be subtypes. For example, with "biased" containers like Optional and Result, it makes sense for the "right" side to be a subtype and the "wrong" side to be explicitly constructed, IMO. If the types of cases overlap, it would also be *ambiguous* which case ought to be constructed when the payload is converted to the enum type</div></div></blockquote><div><br class=""></div><div>Identical case types would definitely be a problem but I don’t think overlapping case types are always a problem. I imagine this conversion working the same as any other ordinary overload resolution for ad-hoc overloads.</div><div><br class=""></div><div>For example let’s assume the numeric types have the desired subtype relationships and I have the following enum for which the cases are subtypes of the enum itself and are given the types of the associated value payloads. This example uses the syntax I used in my value subtyping manifesto for anonymous cases that are subtypes of the enum:</div><div><br class=""></div><div>enum FixedInt {</div><div> case -> Int8</div><div> case -> Int16</div><div> case -> Int32</div><div> case -> Int64</div><div>}</div><div><br class=""></div><div>let fixedInt = Int8(42) // resolves to `FixedInt.int8(42)`</div><div><br class=""></div><div>Now imagine `FixedInt` does not include the `Int8` case:</div><div><br class=""></div><div>let fixedInt = Int8(42) // resolves to `FixedInt.int16(42)`</div><div><br class=""></div><div>I don’t see how this is different than:</div><div><br class=""></div><div>func foo(_ int8: Int8) {}</div><div>func foo(_ int16: Int16) {}</div><div>func foo(_ int16: Int16) {}</div><div>func foo(_ int16: Int16) {}</div><div><br class=""></div><div>foo(Int16(42))</div><div><br class=""></div><div>As long as value subtyping follows the same overload resolution rules that are used for classes and protocol existentials the right thing happens.</div><div><br class=""></div><div>If there is no case with the exact type is getting converted the nearest ancestor type is preferred. In the case of a non-linear type hierarchy (as we already have in protocols) if the overloads available to choose from do not form a strictly linear order *then* we do have ambiguity. In that case it would need to be resolve manually by explicitly specifying which case is intended.</div><div><br class=""></div><div>This doesn’t seem like a problem to me - just ordinary overload resolution.</div><div><br class=""></div><br class=""><blockquote type="cite" class=""><div class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">—remember that enums are sums, not unions, and that's important for composability and uniform behavior with generics. </div></div></blockquote><div><br class=""></div><div>I’ve always thought of enums as nominal discriminated unions. Maybe I’m using the wrong terminology. Can you elaborate on the difference between sums and unions? When you say union are you talking about the kind of thing some people have brought up in the past where any members in common are automatically made available on the union type?</div><br class=""><blockquote type="cite" class=""><div class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">I would be fine allowing enum subtyping with some opt-in attribute, e.g.:</div></div></blockquote><blockquote type="cite" class=""><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class=""></div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">enum Optional<Wrapped> {</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""> sub case some(wrapped)</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""> case none</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">}</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class=""></div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">enum Result<Wrapped> {</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""> sub case ok(wrapped)</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""> case error(Error) // not a subtype</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">}</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class=""></div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">enum JSON {</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""> // OK for these to all be sub-cases, since they don't overlap</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""> sub case string(String), number(Double), array([JSON]), object([String: JSON]), null</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">}</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""><br class=""></div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">enum Either<T, U> {</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""> // Error: sub cases potentially overlap</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class=""> sub case left(T), right(U)</div><div style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;" class="">}</div></blockquote><div><br class=""></div><div>Why can’t we defer the error? `Either<Int, String>` should be allowed but `Either<Int, Int>` should be an error.</div><div><br class=""></div><div>Alternatively, if we agree that overlapping is allowed and overload resolution is used then we could introduce a `!=` constraint to verify correctness at the declaration of `Either`:</div><div><br class=""></div><div>enum Either<T, U> where T != U { … }</div><br class=""><blockquote type="cite" class=""><br class="" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;"><div class="" style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px;">-Joe</div></blockquote></div><br class=""></body></html>