[swift-evolution] Treating an Enum's Cases as Its Subtypes

Niels Andriesse andriesseniels at gmail.com
Tue Feb 21 03:22:27 CST 2017


Exactly
On Tue, 21 Feb 2017 at 19:50, Patrick Pijnappel via swift-evolution <
swift-evolution at swift.org> wrote:

> 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 at swift.org> wrote:
>
>
> On Feb 20, 2017, at 1:53 PM, Matthew Johnson <matthew at anandabits.com>
> wrote:
>
>
> On Feb 20, 2017, at 3:22 PM, Joe Groff <jgroff at apple.com> wrote:
>
>
> On Feb 20, 2017, at 1:04 PM, Matthew Johnson <matthew at anandabits.com>
> wrote:
>
>
> On Feb 20, 2017, at 2:38 PM, Joe Groff <jgroff at apple.com> wrote:
>
>
> On Feb 20, 2017, at 7:32 AM, Matthew Johnson via swift-evolution <
> swift-evolution at swift.org> wrote:
>
>
> On Feb 20, 2017, at 12:40 AM, Niels Andriesse via swift-evolution <
> swift-evolution at 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 at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170221/963a8bf0/attachment.html>


More information about the swift-evolution mailing list