Just to clarify, the proposal doesn't suggest to allow the associated value to be used as a subtype of the enum.
enum Result<T> { case .success(T), .error(Error) } func foo(_ x: Result<Int>) { /* ... */ } func bar(_ x: Result<Int>.success) { /* ... */ } // Not this: foo(5) bar(5) // But rather: foo(.success(5)) bar(.success(5)) Effectively, Result<T>.success would behave like a struct that is a subtype of Result<T>. On Tue, Feb 21, 2017 at 12:50 PM, Joe Groff via swift-evolution < swift-evolution@swift.org> wrote: > > On Feb 20, 2017, at 1:53 PM, Matthew Johnson <matt...@anandabits.com> > wrote: > > > On Feb 20, 2017, at 3:22 PM, Joe Groff <jgr...@apple.com> wrote: > > > On Feb 20, 2017, at 1:04 PM, Matthew Johnson <matt...@anandabits.com> > wrote: > > > On Feb 20, 2017, at 2:38 PM, Joe Groff <jgr...@apple.com> wrote: > > > On Feb 20, 2017, at 7:32 AM, Matthew Johnson via swift-evolution < > swift-evolution@swift.org> wrote: > > > On Feb 20, 2017, at 12:40 AM, Niels Andriesse via swift-evolution < > swift-evolution@swift.org> wrote: > > 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. > > Doing so would allow for constructions such as the following: > > enum Foo { > case a(name: String) > } > > func isA(foo: Foo) -> Bool { > // The old way: > if case .a = foo { return true } > return false > // The new way: > return foo is .a > } > > func printNameIfFooIsA(foo: Foo) -> Bool { > // The old way: > if case let .a(name) = foo { > print(name) > } > // The new way (1): > if let a = foo as? .a { > print(a.name) > } > // The new way (2): > if let name = (foo as? .a)?.name { > print(name) > } > } > > 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. > > The pattern matching capabilities of enums wouldn't be affected by this > proposal. > > 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. > > I believe that this could be the right solution to multiple enum-related > problems that have been brought up repeatedly. > > > 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. > > > 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 > > > 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. > > > Conversions happen at runtime too. `0 as Any as? Either<Int, Int>` > wouldn't have any way to tell what `Either` to form if both arms of the > Either were subtype candidates. An Either<T, U> in <T, U> context can end > up being bound to Either<Int, Int> at runtime and interacting with runtime > casts that way. > > > Hmm. This is unfortunate. > > In cases where T and U overlap and form a linear hierarchy but are not > identical couldn’t the runtime determine the most direct path and choose > that? > > If the compiler prohibited cases with exactly the same types like > `Either<Int, Int>` from being expressed statically how do these types end > up getting formed dynamically? Is there any way those operations could be > failable? > > > > > —remember that enums are sums, not unions, and that's important for > composability and uniform behavior with generics. > > > 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? > > > Sums maintain structure whereas unions collapse it. As a sum, Optional<T> > maintains its shape even when T = Optional<U>. If it were a union, T u Nil > u Nil would collapse to T u Nil, losing the distinction between the inner > and outer nil and leading to problems in APIs that use the outer nil to > communicate meaning about some outer structure, such as asking for the > `first` element of a collection of Optionals. > > > Got it. This is certainly a problem for `Optional`. > > But sometimes this behavior of collapsing the syntactic specification to a > canonical sum type would be very useful. What is the reason we can’t have > something syntactic type expressions like `Int | String`, `Int | String | > String, `String | Int | String | Int`, etc all collapse to the same > canonical structural sum type: > > enum { > sub case int(Int), string(String) > } > > This is how I’ve been thinking about those syntactic types. We already > allow existential types to be formed using syntax that collapses to a > canonical type: > > typealias Existential1 = Protocol1 & Protocol2 > typealias Existential2 = Protocol2 & Existential1 & Protocol 3 & Protocol1 > typealias Existential3 = Existential1 & Protocol3 > > In this example Existential1 and Existential3 are different names for the > same type. > > Is there a reason we can’t have similar syntax that collapses to a > similarly canonical sum type? If we’re going to allow case subtypes this > feels to me like a very natural and useful direction. > > > A couple reasons that come to mind: > > - Most directly, we don't allow abstraction over generic constraints. > `ExistentialN<T, U> = T & U` isn't allowed. As soon as you have abstraction > over either unions or intersections, type checking becomes an unbounded > search problem in the worst case, since every T binding is potentially > equivalent to a T1 & T2 or T1 | T2 with T1 == T2 == T. > > - Sums and unions both imply a matching branch structure in the code > somewhere to handle both possibilities. If the number of actual > possibilities is different in different situations, that's a source of > bugs, such as the overloading of `nil` I mentioned previously. Even if you > did allow generic T & T types, the worst result of someone seeing that as > T1 & T2 is that the operations enabled through conforming to T1 and T2 map > to the same conformance. > > -Joe > > > If we don’t allow it there are two problems: people have to invent a > largely meaningless name for the enum and it is incompatible with any other > similarly structured enum. Neither is a significant problem but they do > add (seemingly) unnecessary friction to the language. > > I wouldn’t expect these to be widely used - they would play a similar role > as tuples - but they would be very appreciated where they are used. > > > -Joe > > > > _______________________________________________ > 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