[swift-evolution] Normalize Enum Case Representation (rev. 2)

John McCall rjmccall at apple.com
Fri Mar 31 12:37:17 CDT 2017


> On Mar 31, 2017, at 3:10 AM, David Hart via swift-evolution <swift-evolution at swift.org> wrote:
> Is there any plan to get this proposal reviewed? It would *really* improve the SwiftPM Package API.

The core team talked about it and have decided to allow it.  I've put it back up for review until the 10th.

Has someone volunteered to work on an implementation for this?  I don't think we at Apple have scheduled any time for it, and if we don't have an implementation within the next few weeks, it's not likely to happen in Swift 4 even if the proposal is approved.

John.

> 
>> On 9 Mar 2017, at 23:42, Daniel Duan via swift-evolution <swift-evolution at swift.org> wrote:
>> 
>> Here’s an updated version with hopefully the correct content. The “overloaded” case feature has been removed and is discussed in the "alternatives considered" section.
>> 
>> 
>> # Normalize Enum Case Representation
>> 
>> * Proposal: [SE-0155][]
>> * Authors: [Daniel Duan][], [Joe Groff][]
>> * Review Manager: [John McCall][]
>> * Status: **Awaiting review**
>> * Previous Revision: [1][Revision 1]
>> 
>> ## Introduction
>> 
>> In Swift 3, associated values of an enum case are represented by a tuple. This
>> implementation causes inconsistencies in case declaration, construction and
>> pattern matching in several places.
>> 
>> Enums, therefore, can be made more "regular" when we replace tuple as the
>> representation of associated case values. This proposal aims to define the
>> effect of doings so on various parts of the language.
>> 
>> Swift-evolution thread: [Normalize Enum Case Representation (rev. 2)][]
>> 
>> ## Motivation
>> 
>> When user declares a case for an enum, a function which constructs the
>> corresponding case value is declared. We'll refer to such functions as _case
>> constructors_ in this proposal.
>> 
>> ```swift
>> enum Expr {
>>   // this case declares the case constructor `Expr.elet(_:_:)`
>>   indirect case elet(locals: [(String, Expr)], body: Expr)
>> }
>> 
>> // f's signature is f(_: _), type is ([(String, Expr)], Expr) -> Expr
>> let f = Expr.elet
>> 
>> // `f` is just a function
>> f([], someExpr) // construct a `Expr.elet`
>> ```
>> 
>> There are many surprising aspects of enum constructors, however:
>> 
>> 1. After [SE-0111][], Swift function's fully qualified name consists of its base
>>  name and all of its argument labels. User can use the full name of the
>>  function at use site. In the example above, `locals` and `body` are currently
>>  not part of the case constructors name, therefore the expected syntax is
>>  invalid.
>> 
>>  ```swift
>>  func f(x: Int, y: Int) {}
>>  f(x: y:)(0, 0) // Okay, this is equivalent to f(x: 0, y: 0)
>>  Expr.elet(locals: body:)([], someExpr) // this doesn't work in Swift 3
>>  ```
>> 2. Case constructors cannot include a default value for each parameter. This
>>  is yet another feature available to functions.
>> 
>> As previous mentioned, these are symptoms of associated values being a tuple
>> instead of having its own distinct semantics. This problem manifests more in
>> Swift 3's pattern matching:
>> 
>> 1. A pattern with a single value would match and result in a tuple:
>> 
>>   ```swift
>>   // this works for reasons most user probably don't expect!
>>   if case .elet(let wat) = anExpr {
>>       eval(wat.body)
>>   }
>>   ```
>> 
>> 2. Labels in patterns are not enforced:
>> 
>>   ```swift
>>   // note: there's no label in the first sub-pattern
>>   if case .elet(let p, let body: q) = anExpr {
>>       // code
>>   }
>>   ```
>> 
>> These extra rules makes pattern matching difficult to teach and to expand to
>> other types.
>> 
>> ## Proposed Solution
>> 
>> We'll add first class syntax (which largely resemble the syntax in Swift 3) for
>> declaring associated values with labels. Tuple will no longer be used to
>> represent the aggregate of associated values for an enum case. This means
>> pattern matching for enum cases needs its own syntax as well (as opposed to
>> piggybacking on tuple patterns, which remains in the language for tuples.).
>> 
>> ## Detailed Design
>> 
>> ### Compound Names For Enum Constructors
>> 
>> Associated values' labels should be part of the enum case's constructor name.
>> 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
>> Expr.elet(locals: [], body: anExpr) // Okay, the Swift 3 way.
>> Expr.elet(locals: body:)([], anExpr) // Okay, equivalent to the previous line.
>> Expr.elet(locals: body:)(locals: 0, body: 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, behaving consistently with functions.
>> 
>> ```swift
>> let f = Expr.elet // f has type ([(String, Expr)], Expr) -> Expr
>> f([], anExpr) // Okay!
>> f(locals: [], body: anExpr) // Won't compile.
>> ```
>> 
>> Enum cases should have distinct *full* names. Therefore, shared base name will
>> be allowed:
>> 
>> ```swift
>> enum SyntaxTree {
>>   case type(variables: [TypeVariable])
>>   case type(instantiated: [Type])
>> }
>> ```
>> 
>> Using only the base name in pattern matching for the previous example would be
>> ambiguous and result in an compile error. In this case, the full name must be
>> supplied to disambiguate.
>> 
>> ```swift
>> case .type // error: ambiguous
>> case .type(variables: let variables) // Okay
>> ```
>> 
>> ### Default Parameter Values For Enum Constructors
>> 
>> 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.
>> 
>> ```swift
>> enum Animation {
>>   case fadeIn(duration: TimeInterval = 0.3) // Okay!
>> }
>> let anim = Animation.fadeIn() // Great!
>> ```
>> 
>> 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;
>> ```
>> 
>> ### Alternative Payload-less Case Declaration
>> 
>> In Swift 3, the following syntax is valid:
>> 
>> ```swift
>> enum Tree {
>>   case leaf() // the type of this constructor is confusing!
>> }
>> ```
>> 
>> `Tree.leaf` has a very unexpected type to most Swift users: `(()) -> Tree`
>> 
>> We propose this syntax become illegal. User must explicitly declare
>> associated value of type `Void` if needed:
>> 
>> ```swift
>> enum Tree {
>>   case leaf(Void)
>> }
>> ```
>> 
>> ### Pattern Consistency
>> 
>> *(The following enum will be used throughout code snippets in this section).*
>> 
>> ```swift
>> indirect enum Expr {
>>   case variable(name: String)
>>   case lambda(parameters: [String], body: Expr)
>> }
>> ```
>> 
>> Compared to patterns in Swift 3, matching against enum cases will follow
>> stricter rules. This is a consequence of no longer relying on tuple patterns.
>> 
>> When an associated value has a label, the sub-pattern must include the label
>> exactly as declared. There are two variants that should look familiar to Swift
>> 3 users. Variant 1 allows user to bind the associated value to arbitrary name in
>> the pattern by requiring the label:
>> 
>> ```swift
>> case .variable(name: let x) // okay
>> case .variable(x: let x) // compile error; there's no label `x`
>> case .lambda(parameters: let params, body: let body) // Okay
>> case .lambda(params: let params, body: let body) // error: 1st label mismatches
>> ```
>> 
>> User may choose not to use binding names that differ from labels. In this
>> variant, the corresponding value will bind to the label, resulting in this
>> shorter form:
>> 
>> ```swift
>> case .variable(let name) // okay, because the name is the same as the label
>> case .lambda(let parameters, let body) // this is okay too, same reason.
>> case .variable(let x) // compiler error. label must appear one way or another.
>> case .lambda(let params, let body) // compiler error, same reason as above.
>> ```
>> 
>> Only one of these variants may appear in a single pattern. Swift compiler will
>> raise a compile error for mixed usage.
>> 
>> ```swift
>> case .lambda(parameters: let params, let body) // error, can not mix the two.
>> ```
>> 
>> Some patterns will no longer match enum cases. For example, all associated
>> values can bind as a tuple in Swift 3, this will no longer work after this
>> proposal:
>> 
>> ```swift
>> // deprecated: matching all associated values as a tuple
>> if case let .lambda(f) = anLambdaExpr {
>>   evaluateLambda(parameters: f.parameters, body: f.body)
>> }
>> ```
>> 
>> ## Source compatibility
>> 
>> Despite a few additions, case declaration remain mostly source-compatible with
>> Swift 3, with the exception of the change detailed in "Alternative Payload-less
>> Case Declaration".
>> 
>> Syntax for case constructor at use site remain source-compatible.
>> 
>> A large portion of pattern matching syntax for enum cases with associated values
>> remain unchanged. But patterns for matching all values as a tuple, patterns that
>> elide the label and binds to names that differ from the labels, patterns that
>> include labels for some sub-patterns but the rest of them are deprecated by this
>> proposal. Therefore this is a source breaking change.
>> 
>> ## Effect on ABI stability and resilience
>> 
>> After this proposal, enum cases may have compound names. This means the standard
>> library will expose different symbols for enum constructors. The name mangling
>> rules should also change accordingly.
>> 
>> ## Alternative Considered
>> 
>> Between case declaration and pattern matching, there exist many reasonable
>> combinations of improvement. On one hand, we can optimize for consistency,
>> simplicity and teachability by bringing in as much similarity between enum and
>> other part of the language as possible. Many decisions in the first revision
>> were made in favor if doing so. Through the feedbacks from swift-evolution, we
>> found that some of the changes impedes the ergonomics of these features too much
>> . In this section, we describe some of the alternatives that were raised and
>> rejected in hope to strike a balance between the two end of the goals.
>> 
>> We discussed allowing user to declare a *parameter name* ("internal names")
>> for each associated value. Such names may be used in various rules in pattern
>> matching. Some feedback suggested they maybe used as property names when we
>> make enum case subtypes of the enum and resembles a struct. This feature is not
>> included in this proposal because parameter names are not very useful *today*.
>> Using them in patterns actually don't improve consistency as users don't use
>> them outside normal function definitions at all. If enum case gains a function
>> body in a future proposal, it'd be better to define the semantics of parameter
>> names then, as opposed to locking it down now.
>> 
>> To maintain ergonomics/source compatibility, we could allow user to choose
>> arbitrary bindings for each associated value. The problem is it makes the
>> pattern deviate a lot from declaration and makes it hard for beginners to
>> understand. This also decrease readability for seasoned users.
>> 
>> Along the same line, a pattern that gets dropped is binding all associated
>> values as a labeled tuple, which tuple pattern allowed in Swift 3. As T.J.
>> Usiyan [pointed out][TJs comment], implementation of the equality protocol 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-deriving for `Equatable` conformance.
>> 
>> The previous revision of this proposal mandated that the labeled form of
>> sub-pattern (`case .elet(locals: let x, body: let y)`) be the only acceptable
>> pattern. Turns out the community considers this to be too verbose in some cases.
>> 
>> A drafted version of this proposal considered allowing "overloaded" declaration
>> of enum cases (same full-name, but with associated values with different types).
>> We ultimately decided that this feature is out of the scope of this proposal.
>> 
>> [SE-0155]: 0155-normalize-enum-case-representation.md
>> [SE-0111]: 0111-remove-arg-label-type-significance.md
>> [Daniel Duan]: https://github.com/dduan
>> [Joe Groff]: https://github.com/jckarter
>> [John McCall]: https://github.com/rjmccall
>> [TJs comment]: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170116/030614.html
>> [Revision 1]: https://github.com/apple/swift-evolution/blob/43ca098355762014f53e1b54e02d2f6a01253385/proposals/0155-normalize-enum-case-representation.md
>> [Normalize Enum Case Representation (rev. 2)]: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170306/033626.html
>> 
>> _______________________________________________
>> 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



More information about the swift-evolution mailing list