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

Xiaodi Wu xiaodi.wu at gmail.com
Wed Mar 8 23:56:54 CST 2017

The rendered version differs from the text appended to your message. I'll
assume the more fully fleshed out version is what you intend to submit.
Three comments/questions:

Enum Case "Overloading"

An enum may contain cases with the same full name but with associated
values of different types. For example:

enum Expr {
    case literal(Bool)
    case literal(Int)

The above cases have overloaded constructors, which follow the same rules
as functions at call site for disambiguation:

// It's clear which case is being constructed in the following.let
aBool: Expr = .literal(false)let anInt: Expr = .literal(42)

User must specify an as expression in sub-patterns in pattern matching, in
order to match with such cases:

case .literal(let value) // this is ambiguouscase .literal(let value
as Bool) // matches `case literal(Bool)`

Comment/question 1: Here, why aren't you proposing to allow `case
.literal(let value: Bool)`? For one, it would seem to be more consistent.
Second, since we still have some use cases where there's Obj-C bridging
magic with `as`, using `as` in this way may run into ambiguity issues if
(for example) you have two cases, one with associated value of type
`String` and the other of type `NSString`. Also, since enum cases are to be
like functions, I assume that the more verbose `as` version would work for
free: `case .literal(let value) as (Bool) -> Expr`?

Payload-less Case Declaration

In Swift 3, the following syntax is valid:

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 declare the "bare" case instead. So it's going to be
the equivalent of

enum Tree {
    case leaf // `()` is optional and does the same thing.}

Comment/question 2: First, if associated values are not to be modeled as
tuples, for backwards compatibility the rare uses of `case leaf()` should
be migrated to `case leaf(())`. Second, to be clear, you are _not_
proposing additional sugar so that a case without an associated value be
equivalent to a case that has an associated value of type `Void`, correct?
You are saying that, with your proposal, both `case leaf()` and `case leaf`
would be regarded as being of type `() -> Tree` instead of the current
`(()) -> Tree`?[The latter (i.e. `() -> Tree`) seems entirely fine. The
former (i.e. additional sugar for `(()) -> Tree`) seems mostly fine, except
that it would introduce an inconsistency with raw values that IMO is
awkward. That is, if I have `enum Foo { case bar }`, it would make case
`bar` have implied associated type `Void`; but, if I have `enum Foo: Int {
case bar }`, would case `bar` have raw value `0` of type `Int` as well as
associated value `()` of type `Void`?]


*(The following enum will be used throughout code snippets in this

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

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:

case .variable(name: let x) // okaycase .variable(x: let x) // compile
error; there's no label `x`case .lambda(parameters: let params, body:
let body) // Okaycase .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:

case .variable(let name) // okay, because the name is the same as the
labelcase .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.

Comment/question 3: Being a source-breaking change, that requires extreme
justification, and I just don't think there is one for this rule. The
perceived problem being addressed (that one might try to bind `parameters`
to `body` and `body` to `parameters`) is unchanged whether enum cases are
modeled as tuples or functions, so aligning enum cases to functions is not
in and of itself justification to revisit the issue of whether to try to
prohibit this. In fact, I think the proposed solution suffers from two
great weaknesses. First, it seems ad-hoc. Consider this: if enum cases are
to be modeled as functions, then I should be able to write something
intermediate between the options above; namely: `case .variable(name:)(let
x)`. Since `.variable` unambiguously refers to `.variable(name:)`, I should
also be allowed to write `.variable(let x)` just as I am now. Second, it seems
unduly restrictive. If, in the containing scope, I have a variable named
`body` that I don't want to shadow, this rule would force me to either
write the more verbose form or deal with shadowing `body`. If a person opts
for the shorter form, they are choosing not to use the label.

Only one of these variants may appear in a single pattern. Swift compiler
will raise a compile error for mixed usage.

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

// deprecated: matching all associated values as a tupleif case let
.lambda(f) = anLambdaExpr {
    evaluateLambda(parameters: f.parameters, body: f.body)

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170308/7f34c891/attachment.html>

More information about the swift-evolution mailing list