OK. I've taken the most recent changes from this thread and put together a 
draft for a proposal.

https://gist.github.com/timshadel/5a5a8e085a6fd591483a933e603c2562 
<https://gist.github.com/timshadel/5a5a8e085a6fd591483a933e603c2562>

I'd appreciate your review, especially to ensure I've covered all the important 
scenarios. I've taken the 3 associated value scenarios (none, unlabeled, 
labeled) and shown them in each example (calculated value, func, default, 
error). I've included the raw text below, without any syntax highlighting.

My big question is: does the error case in the last example affect ABI 
requirements, in order to display the error at the correct case line? I assume 
it doesn't, but that's an area I don't know well.

Thanks!

Tim

===============

# Enum Case Blocks

* Proposal: SE-XXXX
* Authors: [Tim Shadel](https://github.com/timshadel)
* Review Manager: TBD
* Status: **TBD**

## Motivation

Add an optional syntax to declare all code related to a single `case` in one 
spot. For complex `enum`s, this makes it easier to ensure that all the pieces 
mesh coherently across that one case, and to review all logic associated with a 
single `case`. This syntax is frequently more verbose in order to achieve a 
more coherent code structure, so its use will be most valuable in complex enums.

Swift-evolution thread: [Consolidate Code for Each Case in 
Enum](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170102/029966.html)

## Proposed solution

Allow an optional block directly after the `case` declaration on an `enum`. 
Construct a hidden `switch self` statement for each calculated value or `func` 
defined in any case block. Use the body of each such calculated value in the 
hidden `switch self` under the appropriate case. Because switch statements must 
be exhaustive, the calculated value or `func` must be defined in each case 
block or have a `default` value to avoid an error. Defining the `func` or 
calculated value outside a case block defines the default case for the `switch 
self`. To reference an associated value within any of the items in a case block 
requires the value be labeled, or use a new syntax `case(_ label: Type)` to 
provide a local-only name for the associated value.

## Examples

All examples below are evolutions of this simple enum.

```swift
enum AuthenticationState {
    case invalid
    case expired(Date)
    case validated(token: String)
}
```

### Basic example

First, let's add `CustomStringConvertible` conformance to our enum.

```swift
enum AuthenticationState: CustomStringConvertible {

    case invalid {
        var description: String { return "Authentication invalid." }
    }

    case expired(_ expiration: Date) {
        var description: String { return "Authentication expired at 
\(expiration)." }
    }

    case validated(token: String) {
        var description: String { return "The authentication token is 
\(token)." }
    }

}
```

This is identical to the following snippet of Swift 3 code:

```swift
enum AuthenticationState: CustomStringConvertible {

    case invalid
    case expired(Date)
    case validated(token: String)

    var description: String {
        switch self {
        case invalid:
            return "Authentication invalid."
        case let expired(expiration):
            return "Authentication expired at \(expiration)."
        case let validated(token):
            return "The authentication token is \(token)."
        }
    }

}
```

### Extended example

Now let's have our enum conform to this simple `State` protocol, which expects 
each state to be able to update itself in reaction to an `Event`. This example 
begins to show how this optional syntax give better coherence to the enum code 
by placing code related to a single case in a single enclosure.

```swift
protocol State {
    mutating func react(to event: Event)
}

enum AuthenticationState: State, CustomStringConvertible {

    case invalid {
        var description: String { return "Authentication invalid." }

        mutating func react(to event: Event) {
            switch event {
            case let login as UserLoggedIn:
                self = .validated(token: login.token)
            default:
                break
            }
        }
    }

    case expired(_ expiration: Date) {
        var description: String { return "Authentication expired at 
\(expiration)." }

        mutating func react(to event: Event) {
            switch event {
            case let refreshed as TokenRefreshed:
                self = .validated(token: refreshed.token)
            default:
                break
            }
        }
    }

    case validated(token: String) {
        var description: String { return "The authentication token is 
\(token)." }

        mutating func react(to event: Event) {
            switch event {
            case let expiration as TokenExpired:
                print("Expiring token: \(token)")
                self = .expired(expiration.date)
            case _ as TokenRejected:
                self = .invalid
            case _ as UserLoggedOut:
                self = .invalid
            default:
                break
            }
        }
    }

}
```

This becomes identical to the following Swift 3 code:

```swift
enum AuthenticationState: State, CustomStringConvertible {

    case invalid
    case expired(Date)
    case validated(token: String)

    var description: String {
        switch self {
        case invalid:
            return "Authentication invalid."
        case let expired(expiration):
            return "Authentication expired at \(expiration)."
        case let validated(token):
            return "The authentication token is \(token)."
        }
    }

    mutating func react(to event: Event) {
        switch self {
        case invalid: {
            switch event {
            case let login as UserLoggedIn:
                self = .validated(token: login.token)
            default:
                break
            }
        }
        case let expired(expiration) {
            switch event {
            case let refreshed as TokenRefreshed:
                self = .validated(token: refreshed.token)
            default:
                break
            }
        }
        case let validated(token) {
            switch event {
            case let expiration as TokenExpired:
                print("Expiring token: \(token)")
                self = .expired(expiration.date)
            case _ as TokenRejected:
                self = .invalid
            case _ as UserLoggedOut:
                self = .invalid
            default:
                break
            }
        }
    }

}
```

### Default case example

Let's go back to the simple example to demonstrate declaring a default case.

```swift
enum AuthenticationState: CustomStringConvertible {

    var description: String { return "" }

    case invalid
    case expired(Date)
    case validated(token: String) {
        var description: String { return "The authentication token is 
\(token)." }
    }

}
```

Is identical to this Swift 3 code:

```swift
enum AuthenticationState: CustomStringConvertible {

    case invalid
    case expired(Date)
    case validated(token: String)

    var description: String {
        switch self {
        case let validated(token):
            return "The authentication token is \(token)."
        default:
            return ""
        }
    }

}
```

### Error example

Finally, here's what happens when a case fails to add a block when no default 
is defined.

```swift
enum AuthenticationState: CustomStringConvertible {

    case invalid  <<< error: description must be exhaustively defined. Missing 
block for case .invalid.

    case expired(Date)  <<< error: description must be exhaustively defined. 
Missing block for case .expired.

    case validated(token: String) {
        var description: String { return "The authentication token is 
\(token)." }
    }

}
```

## Source compatibility

No source is deprecated in this proposal, so source compatibility should be 
preserved.

## Effect on ABI stability

Because the generated switch statement should be identical to one that can be 
generated with Swift 3, I don't foresee effect on ABI stability.

Question: does the error case above affect ABI requirements, in order to 
display the error at the correct case line?

## Alternatives considered

Use of the `extension` keyword was discussed and quickly rejected for numerous 
reasons.


> On Jan 9, 2017, at 2:22 PM, Tony Allevato <[email protected]> wrote:
> 
> I like that approach a lot (and it would be nice to use separate labels vs. 
> argument names in the case where they do have labels, too).
> 
> Enum cases with associated values are really just sugar for static methods on 
> the enum type *anyway* with the added pattern matching abilities, so unifying 
> the syntax seems like a positive direction to go in.
> 
> 
> On Mon, Jan 9, 2017 at 1:20 PM Tim Shadel <[email protected] 
> <mailto:[email protected]>> wrote:
> Yeah, that's much nicer than what I just sent! :-D
> 
> > On Jan 9, 2017, at 2:16 PM, Sean Heber <[email protected] 
> > <mailto:[email protected]>> wrote:
> >
> > I can’t speak for Tim, but I’d suggest just unifying the case syntax with 
> > functions so they become:
> >
> > case foo(_ thing: Int)
> >
> > And if you don’t actually need to ever *use* it by name in your enum 
> > properties/functions (if you even have any), then you could leave it out 
> > and write it like it is now, but that’d become “sugar”:
> >
> > case foo(Int)
> >
> > l8r
> > Sean
> >
> >
> >> On Jan 9, 2017, at 3:11 PM, Tony Allevato <[email protected] 
> >> <mailto:[email protected]>> wrote:
> >>
> >> Ah, my apologies—the syntax highlighting in the thread was throwing off my 
> >> e-mail client and I was having trouble reading it.
> >>
> >> Associated values don't necessarily have to have names: I can write "case 
> >> .foo(Int)". Since your examples use the associated value label as the name 
> >> of the value inside the body, how would you handle those label-less values?
> >>
> >>
> >> On Mon, Jan 9, 2017 at 1:06 PM Tim Shadel <[email protected] 
> >> <mailto:[email protected]>> wrote:
> >> There are examples of associated values in the proposed syntax. Which 
> >> parts should I provide more detail on?
> >>
> >>> On Jan 9, 2017, at 1:43 PM, Tony Allevato via swift-evolution 
> >>> <[email protected] <mailto:[email protected]>> wrote:
> >>>
> >>> While I do like the consolidated syntax more than most of the 
> >>> alternatives I've seen to address this problem, any proposed solution 
> >>> also needs to address how it would work with cases that have associated 
> >>> values. That complicates the syntax somewhat.
> >>>
> >>>
> >>> On Mon, Jan 9, 2017 at 12:37 PM Sean Heber via swift-evolution 
> >>> <[email protected] <mailto:[email protected]>> wrote:
> >>>
> >>>> On Jan 9, 2017, at 2:28 PM, Guillaume Lessard via swift-evolution 
> >>>> <[email protected] <mailto:[email protected]>> wrote:
> >>>>
> >>>>
> >>>>> On 9 janv. 2017, at 10:54, Tim Shadel via swift-evolution 
> >>>>> <[email protected] <mailto:[email protected]>> wrote:
> >>>>>
> >>>>> Enums get large, and they get complicated because the code for each 
> >>>>> case gets sliced up and scattered across many functions. It becomes a 
> >>>>> "one of these things is not like the other" situation because writing 
> >>>>> functions inside enums is unlike writing functions in any other part of 
> >>>>> Swift code.
> >>>>
> >>>> The problem I see with this is that enums and their functions inherently 
> >>>> multiply each other. If I have 3 cases and 3 functions or properties, 
> >>>> there are 9 implementation details, no matter how they're organized. 
> >>>> There can be 3 functions/properties, each with a 3-case switch, or there 
> >>>> can be 3 enum cases each with 3 strange, partial functions/properties.
> >>>>
> >>>> I can see why someone might prefer one over the other, but is either way 
> >>>> truly better? The current way this works at least has the merit of not 
> >>>> requiring a special dialect for enums.
> >>>
> >>> I’m not sure how to argue this, but I feel pretty strongly that something 
> >>> more like this proposed organization *is* actually better. That said, I 
> >>> do not think this conflicts with the current design of enums, however, so 
> >>> this is likely purely additive. The current design makes some situations 
> >>> almost comically verbose and disorganized, IMO, but it *is* right for 
> >>> other situations. We may want to have both.
> >>>
> >>> l8r
> >>> Sean
> >>> _______________________________________________
> >>> swift-evolution mailing list
> >>> [email protected] <mailto:[email protected]>
> >>> https://lists.swift.org/mailman/listinfo/swift-evolution 
> >>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
> >>> _______________________________________________
> >>> swift-evolution mailing list
> >>> [email protected] <mailto:[email protected]>
> >>> https://lists.swift.org/mailman/listinfo/swift-evolution 
> >>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
> >>
> >
> 

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to