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

Derrick Ho wh1pch81n at gmail.com
Sun Jan 8 01:50:58 CST 2017


Correct me if I am wrong but currently there are only two ways to extract
the value of an associated type.

// 1
switch n {
case foo(let value):
print(value)
}

// 2
if case foo(let value) = n {
print(value)
}

I think it would be nice to have another way. Maybe a tuple-like pattern.

let value = n.foo.0 // returns value as an optional.

If this can already be done, ignore me.


On Sun, Jan 8, 2017 at 2:20 AM Robert Widmann via swift-evolution <
swift-evolution at swift.org> wrote:

> I don't think I've ever wanted to distribute the patterns of a switch
> statement across multiple files.  It seems like you want an enum of enums
> if the code you're writing needs this kind of chunking.  Distributing cases
> is also far more brittle than the existing local switch; failing to include
> a file in the build that covers necessary cases now becomes a module-level
> error rather than a statement-local one.  Finally, the proposal seems to do
> the opposite of consolidate and simplify code by introducing quite a lot of
> syntax around what it aims to do, and by muddying the meaning of a keyword
> that was previously only meant for types.
>
> A few conceptual questions:
>
> How does this interact with versioned cases?
> What about enums that carry values?
>
> ~Robert Widmann
>
> 2017/01/06 23:59、Tim Shadel via swift-evolution <swift-evolution at swift.org>
> のメッセージ:
>
> Idea: Consolidate the Code for Each Case in an Enum
>
>>
> # Motivation:
>
>>
> Consolidate all code related to a single enum case in one spot. This makes
> it easier to ensure that all the pieces mesh coherently across that one
> case.
>
>>
> # Background:
>
>>
> Enum cases _feel_ like separately defined, but tightly related structs
> because each case can have distinct associated values. They have special
> privileges that a family of structs doesn't have, like `self = .otherCase`.
> Enums are really awesome.
>
>>
> # Proposed Solution:
>
>>
> Any `func` or dynamic `var` that provides a unique response per `case`
> uses a `switch` to do so. I propose to hide that standard `switch` behind
> some syntactic sugar. Possibly `extension MyEnum.myCase`, assuming that
> nothing extra is allowed there (protocol conformance, generic constraints,
> etc.).
>
>>
> Here's a typical example of a (simplified) enum that represents 2 states,
> and conforms to 2 protocols, each requiring different dynamic values based
> on the case of the enum. In both places, an outer `switch` is used to
> select the current enum case, and the logic within each branch further
> determines the value returned.
>
>>
> ```
>
> protocol State {
>
>     mutating func react(to event: Event)
>
> }
>
>>
> enum TokenState: State, CustomStringConvertible {
>
>>
>     case expired(at: Date)
>
>     case validated(token: String)
>
>>
>     var description: String {
>
>       switch self {
>
>         case let .expired(at):
>
>             return "Expired at \(at)"
>
>         case let .validated(token):
>
>             return "Token \(token) has been validated."
>
>       }
>
>     }
>
>>
>     mutating func react(to event: Event) {
>
>         switch self {
>
>         case .expired:
>
>             switch event {
>
>             case _ as TokenRefreshed:
>
>                 self = .validated(token: event.token)
>
>             default:
>
>                 break
>
>             }
>
>         case .validated:
>
>             switch event {
>
>             case _ as TokenRejected:
>
>                 self = .expired(at: Date())
>
>             case _ as UserLoggedOut:
>
>                 self = .expired(at: Date())
>
>             default:
>
>                 break
>
>             }
>
>         }
>
>     }
>
>
>
> }
>
> ```
>
>>
> If we instead allow all the code for each enum case to be consolidated,
> this new code looks much more like the rest of the code we write in Swift.
> Real world enums frequently have many more cases, and as the number of enum
> cases grows consolidating all their logic is increasingly helpful. The
> following proposal is identical to the code above, it simply "hides" the
> outer switch statement of each value.
>
>>
> ```
>
> enum TokenState: State, CustomStringConvertible {
>
>     case expired(at: Date)
>
>     case validated(token: String)
>
> }
>
>>
> extension TokenState.expired {
>
>>
>     var description: String {
>
>       return "Token expired at \(self.at)"
>
>     }
>
>
>
>     mutating func react(to event: Event) {
>
>         switch event {
>
>         case _ as TokenRefreshed:
>
>             self = .untested(token: event.token)
>
>         default:
>
>             break
>
>         }
>
>     }
>
>>
> }
>
>>
> extension TokenState.validated {
>
>
>
>     var description: String {
>
>       return "Token \(self.token) has been validated."
>
>     }
>
>
>
>     mutating func react(to event: Event) {
>
>         switch event {
>
>         case _ as TokenRejected:
>
>             self = .expired(at: Date())
>
>         case _ as UserLoggedOut:
>
>             self = .expired(at: Date())
>
>         default:
>
>             break
>
>         }
>
>     }
>
>
>
> }
>
> ```
>
>>
> I've also shown automatic binding of each case's associated values to
> properties available on `self` ... but maybe it's better if they're bound
> to variable references captured the way a closure does. I'm not an expert
> in this part.
>
>>
> Back to the meat of the idea, what happens when a case isn't extended, or
> only partially extended? Because it's simply a fancy `switch`, it still
> must be exhaustive or provide a `default` branch.
>
>>
> ```
>
> extension TokenState.expired {
>
>>
>     var description: String {
>
>       return "Token expired at \(self.at)"
>
>     }
>
>
>
>     <<< error: react(to:) must be exhaustively defined. Missing
> implementation for case .expired
>
> }
>
> ```
>
>>
> Can be mitigated with:
>
>>
> ```
>
> enum TokenState: State, CustomStringConvertible {
>
>     case expired(at: Date)
>
>     case validated(token: String)
>
>>
>     // This becomes the `default` branch in the generated `switch`
>
>     mutating func react(to event: Event) {
>
>         print("Ignoring \(event) in case \(self)")
>
>     }
>
> }
>
> ```
>
>>
> Note that this implementation for the `default` branch is just that. This
> is not creating a superclass/subclass relationship between the `enum` and
> the `case`, it's merely a convenient way to construct a `switch` statement.
> I'm not proposing to deprecate any existing source, merely introduce a more
> convenient form of a very typical pattern, so I hope it is
> source-compatible by the definition you guys are using.
>
> Thoughts?
>
> --Tim
>
> _______________________________________________
> 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/20170108/e3a6f423/attachment.html>


More information about the swift-evolution mailing list