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
