[swift-evolution] Consolidate Code for Each Case in Enum

Derrick Ho wh1pch81n at gmail.com
Wed Jan 11 08:48:49 CST 2017


Interesting proposal Tim. So instead of repeating the enum cases multiple
times we repeat the functions multiple times?

I feel like this merely flipping the complexity but not really getting rid
of it.
On Tue, Jan 10, 2017 at 8:08 PM Tim Shadel via swift-evolution <
swift-evolution at swift.org> wrote:

> 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
>
> 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 <tony.allevato at gmail.com> 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 <timshadel at gmail.com> wrote:
>
> Yeah, that's much nicer than what I just sent! :-D
>
> > On Jan 9, 2017, at 2:16 PM, Sean Heber <sean at fifthace.com> 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 <tony.allevato at gmail.com>
> 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 <timshadel at gmail.com> 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 <
> swift-evolution at swift.org> 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 <
> swift-evolution at swift.org> wrote:
> >>>
> >>>> On Jan 9, 2017, at 2:28 PM, Guillaume Lessard via swift-evolution <
> swift-evolution at swift.org> wrote:
> >>>>
> >>>>
> >>>>> On 9 janv. 2017, at 10:54, Tim Shadel via swift-evolution <
> swift-evolution at swift.org> 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
> >>> 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
> >>
> >
>
>
> _______________________________________________
> 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/20170111/57f00c64/attachment.html>


More information about the swift-evolution mailing list