[swift-evolution] [draft] Compound Names For Enum Cases

Matthew Johnson matthew at anandabits.com
Mon Jan 23 17:49:01 CST 2017



Sent from my iPad

> On Jan 23, 2017, at 3:32 PM, Joe Groff via swift-evolution <swift-evolution at swift.org> wrote:
> 
> This looks pretty good! It might be worth calling out explicitly that matching a payloaded case by name alone still works, e.g.:
> 
> enum Foo { case foo(Int), bar(x: Int) }
> 
> switch Foo.foo(0) {
> case .foo:
>   break
> case .bar(x:):
>   break
> }

In your example would 'bar(x:)' be required or would a naked 'bar' also be valid?  I'm guessing it would not be valid which strikes me as slightly unfortunate.  This would create some unpleasant verbosity in some places that isn't required today.  (Incidentally, a nontrivial amount of this code would be in easily derivable "isSameCase" equivalence relations that compare the case used but not the associated values)

Another question - if labels become part of the case name does that mean we can "overload" the base name?

enum Foo {
   case bar(x: Int)
   case bar(y: Int)
}

The example is intentionally problematic because I'm not sure this would be a good idea, but more realistic examples may be possible with cases more meaningfully distinguished by associated value labels.  

This is an idea that naturally follows with a move to a more function-like model of enum cases with labels being part of the name so it's worth discussing whether or not it should be allowed.


> 
> -Joe
> 
>> On Jan 23, 2017, at 11:38 AM, Daniel Duan <daniel at duan.org> wrote:
>> 
>> I’ve incorporated feedbacks from this thread.
>> 
>> Rendered: https://github.com/dduan/swift-evolution/blob/compound-names-for-enum-cases/proposals/NNNN-Normalize-Enum-Case-Representation.md
>> 
>>>> 
>> # Normalize Enum Case Representation
>> 
>> * Proposal: [SE-NNNN][]
>> * Authors: [Daniel Duan][], [Joe Groff][]
>> * Review Manager: TBD
>> * Status: **Awaiting review**
>> 
>> ## Introduction
>> 
>> In Swift 3, associated values for an enum case are represented by
>> a labeled-tuple. This has several undesired effects: inconsistency in enum value
>> construction syntax, many forms of pattern matching, missing features such as
>> specifying default value and missed opportunity for layout improvements.
>> 
>> This proposal aims to make enums more "regular" by replacing tuple as the
>> representation of associated values, making declaration and construction of enum
>> cases more function-like.
>> 
>> Swift-evolution thread: [Compound Names For Enum Cases][SE Thread]
>> 
>> ## Motivation
>> 
>> **Each enum case declares a function that can be used to create a corresponding
>> value. To users who expect these functions to behave "normally", surprises
>> await.**
>> 
>> 1. Associated value labels aren't part of the function name.
>> 
>>     After [SE-0111][] Swift function's fully qualified name consists of its
>>     base-name and all argument labels. As an illustration, one can invoke
>>     a function with its full name:
>> 
>>     ```swift
>>     func f(x: Int, y: Int) {}
>>     f(x: y:)(0, 0) // Okay, this is equivalent to f(x: 0, y: 0)
>>     ```
>> 
>>     This, however, cannot be done when enum cases with associated value were
>>     constructed:
>> 
>>     ```swift
>>     enum Foo {
>>         case bar(x: Int, y: Int)
>>     }
>>     Foo.bar(x: y:)(0, 0) // Does not compile as of Swift 3
>>     ```
>> 
>>     Here, `x` and `y` are labels of bar's payload (a tuple), as opposed to being
>>     part of the case's formal name. This is inconsistent with rest of the
>>     language.
>> 
>> 2. Default value for parameters isn't available in case declarations.
>> 
>>     ```swift
>>     enum Animation {
>>         case fadeIn(duration: TimeInterval = 0.3) // Nope!
>>     }
>>     let anim = Animation.fadeIn() // Would be nice, too bad!
>>     ```
>> 
>> **Associated values being a tuple complicates pattern matching.**
>> 
>> The least unexpected pattern to match a `bar` value is the following:
>> 
>> ```swift
>> if case let .bar(x: p, y: q) = Foo.bar(x: 0, y: 1) {
>>     print(p, q) // 0 1
>> }
>> ```
>> 
>> In Swift 3, there are a few alternatives that may not be obvious to new users.
>> 
>> 1. A pattern with a single value would match and result in a tuple:
>> 
>>     ```swift
>>     if case let .bar(wat) = Foo.bar(x: 0, y: 1) {
>>         print(wat.y) // 1
>>     }
>>     ```
>> 
>> 2. Labels in patterns are not enforced:
>> 
>>     ```swift
>>     // note: there's no label in the following pattern
>>     if case let .bar(p, q) = Foo.bar(x: 0, y: 1) {
>>         print(p, q) // 0 1
>>     }
>>     ```
>> 
>> These complex rules makes pattern matching difficult to teach and to expand to
>> other types.
>> 
>> **Moving away from tuple-as-associated-value also give us opportunity to improve
>> enum's memory layout** since each associated value would no longer play double
>> duty as part of the tuple's memory layout.
>> 
>> ## Proposed Solution
>> 
>> When a enum case has associated values, they will no longer form a tuple. Their
>> labels will become part of the case's declared name. Patterns matching such
>> value must include labels in order matching the declaration.
>> 
>> This proposal also introduce the ability to include a default value for each
>> associated value in the declaration.
>> 
>> ## Detailed Design
>> 
>> ### Make associated value labels part of case's name
>> When labels are present in enum case's payload, they will become part of case's
>> declared name instead of being labels for fields in a tuple.  In details, when
>> constructing an enum value with the case name, label names must either be
>> supplied in the argument list it self, or as part of the full name.
>> 
>> ```swift
>> Foo.bar(x: 0, y: 0) // Okay, the Swift 3 way.
>> Foo.bar(x: y:)(0, 0) // Equivalent to the previous line.
>> Foo.bar(x: y:)(x: 0, y: 0) // This would be an error, however.
>> ```
>> 
>> Note that since the labels aren't part of a tuple, they no longer participate in
>> type checking, similar to functions:
>> 
>> ```swift
>> let f = Foo.bar // f has type (Int, Int) -> Foo
>> f(0, 0) // Okay!
>> f(x: 0, y: 0) // Won't compile.
>> ```
>> 
>> ### Add default value in enum case declarations
>> 
>> From a user's point view, declaring an enum case should remain the same as Swift
>> 3 except now it's possible to add `= expression` after the type of an
>> associated value to convey a default value for that field. Updated syntax:
>> 
>> ```ebnf
>> union-style-enum-case = enum-case-name [enum-case-associated-value-clause];
>> enum-case-associated-value-clause = "(" ")"
>>                                   | "(" enum-case-associated-value-list ")";
>> enum-case-associated-value-list = enum-associated-value-element
>>                                 | enum-associated-value-element ","
>>                                   enum-case-associated-value-list;
>> enum-case-associated-value-element = element-name type-annotation
>>                                      [enum-case-element-default-value-clause]
>>                                    | type [enum-case-element-default-value-clause];
>> element-name = identifier;
>> enum-case-element-default-value-clause = "=" expression;
>> ```
>> 
>> ### Simplify pattern matching rules on enums
>> Syntax for enum case patterns will be the following:
>> 
>> ```ebnf
>> enum-case-pattern = [type-identifier] "." enum-case-name [enum-case-associated-value-pattern];
>> enum-case-associated-value-pattern = "(" [enum-case-associated-value-list-pattern] ")";
>> enum-case-associated-value-list-pattern = enum-case-associated-value-list-pattern-element
>>                                         | enum-case-associated-value-list-pattern-element ","
>>                                           enum-case-associated-value-list-pattern;
>> enum-case-associated-value-list-element = pattern | identifier ":" pattern;
>> ```
>> 
>> … and `case-associated-value-pattern` will be added to the list of various
>> `pattern`s.
>> 
>> Note that `enum-case-associated-value-pattern` is identical to `tuple-pattern`
>> except in names. It is introduced here to denote semantic difference between the
>> two.  Whereas the syntax in Swift 3 allows a single `tuple-pattern-element` to
>> match the entire case payload, the number of
>> `enum-case-associated-value-list-pattern-element`s must be equal to that of
>> associated value of the case in order to be a match. This means this example
>> will be deprecated under this proposal:
>> 
>> ```swift
>> if case let .bar(wat) = Foo.bar(x: 0, y: 1) { // syntax error
>>     // …
>> }
>> ```
>> 
>> Further, `identifier` in `enum-case-associated-value-list-pattern-element` must
>> be the same as the label of corresponding associated value intended for the
>> match. So this will be deprecated as well:
>> 
>> ```swift
>> if case let .bar(p, q) = Foo.bar(x: 0, y: 1) { // missing `x:` and `y:`
>>     // …
>> }
>> ```
>> 
>> ## Source compatibility
>> 
>> As detailed in the previous section, this proposal deprecates certain pattern
>> matching syntax.
>> 
>> Other changes to the syntax are additive and source compatible with Swift 3.
>> 
>> ## Effect on ABI stability and resilience
>> 
>> After this proposal, enum cases may have compound names, which would be mangled
>> differently than Swift 3.
>> 
>> The compiler may also layout enums differently now that payloads are not
>> constrained by having to be part of a tuple.
>> 
>> ## Alternative Considered
>> 
>> To maintain maximum source compatibility, we could introduce a rule that matches
>> all associated values to a labeled tuple. As T.J. Usiyan
>> [pointed out][TJs comment], implementation of the equality protocal would be
>> simplified due to tuple's conformance to `Equatable`. This feature may still be
>> introduced with alternative syntax (perhaps related to splats) later without
>> source-breakage.  And the need to implement `Equatable` may also disappear with
>> auto-devriving for `Equitable` conformance.
>> 
>> A syntax that did stay for source compatibility is allowing `()` in patterns
>> that match enum cases without associated values:
>> 
>> ```swift
>> if let case .x() = Foo.baz { // … }
>> ```
>> 
>> We could remove this syntax as it would make the pattern look more consistent to
>> the case's declaration.
>> 
>> [SE-0111]: https://github.com/apple/swift-evolution/blob/master/proposals/0111-remove-arg-label-type-significance.md
>> [Daniel Duan]: https://github.com/dduan
>> [Joe Groff]: https://github.com/jckarter
>> [SE-NNNN]: NNNN-Normalize-Enum-Case-Representation.md
>> [TJs comment]: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170116/030614.html
>> [SE Thread]: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170116/030477.html
>> 
>>> On Jan 19, 2017, at 10:37 AM, Daniel Duan via swift-evolution <swift-evolution at swift.org> wrote:
>>> 
>>> Hi all,
>>> 
>>> Here’s a short proposal for fixing an inconsistency in Swift’s enum. Please share you feedback :)
>>> 
>>> (Updating/rendered version: https://github.com/dduan/swift-evolution/blob/compound-names-for-enum-cases/proposals/NNNN-Compound-Names-For-Enum-Cases.md)
>>> 
>>> 
>>> ## Introduction
>>> 
>>> Argument labels are part of its function's declaration name. An enum case
>>> declares a function that can be used to construct enum values. For cases with
>>> associated values, their labels should be part of the constructor name, similar
>>> to "normal" function and methods. In Swift 3, however, this is not true. This
>>> proposal aim to change that.
>>> 
>>> ## Motivation
>>> 
>>> After SE-0111, Swift function's fully qualified name consists of its base name
>>> and all argument labels. As a example, one can invoke a function with its
>>> fully name:
>>> 
>>> ```swift
>>> func f(x: Int, y: Int) {}
>>> 
>>> f(x: y:)(0, 0) // Okay, this is equivalent to f(x: 0, y: 0)
>>> ```
>>> 
>>> This, however, is not true when enum cases with associated value were
>>> constructed:
>>> 
>>> ```swift
>>> enum Foo {
>>>     case bar(x: Int, y: Int)
>>> }
>>> 
>>> Foo.bar(x: y:)(0, 0) // Does not compile as of Swift 3
>>> ```
>>> 
>>> Here, the declared name for the case is `foo`; it has a tuple with two labeled
>>> fields as its associated value. `x` and `y` aren't part of the case name. This
>>> inconsistency may surprise some users.
>>> 
>>> Using tuple to implement associated value also limits us from certain layout
>>> optimizations as each payload need to be a tuple first, as opposed to simply be
>>> unique to the enum.
>>> 
>>> ## Proposed solution
>>> 
>>> Include labels in enum case's declaration name. In the last example, `bar`'s
>>> full name would become `bar(x:y:)`, `x` and `y` will no longer be labels in a
>>> tuple. The compiler may also stop using tuple to represent associated values.
>>> 
>>> ## Detailed design
>>> 
>>> When labels are present in enum cases, they are now part of case's declared name
>>> instead of being labels for fields in a tuple. In details, when constructing an
>>> enum value with the case name, label names must either be supplied in the
>>> argument list it self, or as part of the full name.
>>> 
>>> ```swift
>>> Foo.bar(x: 0, y: 0) // Okay, the Swift 3 way.
>>> Foo.bar(x: y:)(0, 0) // Equivalent to the previous line.
>>> Foo.bar(x: y:)(x: 0, y: 0) // This would be an error, however.
>>> ```
>>> 
>>> Note that since the labels aren't part of a tuple, they no longer participate in
>>> type checking, similar to functions:
>>> 
>>> ```swift
>>> let f = Foo.bar // f has type (Int, Int) -> Foo
>>> f(0, 0) // Okay!
>>> f(x: 0, y: 0) // Won't compile.
>>> ```
>>> 
>>> ## Source compatibility
>>> 
>>> Since type-checking rules on labeled tuple is stricter than that on function
>>> argument labels, existing enum value construction by case name remain valid.
>>> This change is source compatible with Swift 3.
>>> 
>>> ## Effect on ABI stability and resilience
>>> 
>>> This change introduces compound names for enum cases, which affects their
>>> declaration's name mangling.
>>> 
>>> The compiler may also choose to change enum payload's representation from tuple.
>>> This may open up more space for improving enum's memory layout.
>>> 
>>> ## Alternatives considered
>>> 
>>> Keep current behaviors, which means we live with the inconsistency.
>>> 
>>> _______________________________________________
>>> 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/20170123/70a936e7/attachment.html>


More information about the swift-evolution mailing list