While my main focus is on the proposal about consolidating cases, I had notes
about almost that exact syntax... :-D
enum TokenState: State {
let id: Int
let token: String
case loading {
let startedAt: Date
var description: String {
return "Loading token from disk"
}
}
case none {
let reason: String
}
}
The main thing to note is the difference between stored properties (well,
associated values) and calculated ones. I think this new syntax can be done
entirely within enum's current capabilities. The `let` statements outside the
case simply define some associated values used by all cases. The `let`
statements inside the cases declare additional associated values used for only
that case.
In all areas, anything that's a `var` is dynamic, and so repeats type
information in all `case` blocks because that seems like the least disruptive
way to do it.
To Guillaume's point, this actually makes enums have _less_ special syntax, and
treat them more like a closed family of structs with special powers.
BUT, again my focus is first on consolidating the case logic.
—Tim
> On Jan 9, 2017, at 11:11 AM, Sean Heber <[email protected]> wrote:
>
> I would like something along these lines. I would suggest going farther and
> do something like this if possible to avoid repeating the type information
> all over the place:
>
> enum OneOnOneField: Int {
> let title: String
> let placeholder: String
> let image: UIImage
>
> case agenda {
> title: NSLocalizedString("Agenda", comment: "One on one field
> header")
> placeholder: NSLocalizedString("Add an agenda", comment: "One
> on one field placeholder”)
> image: #imageLiteral(resourceName: "Agenda-Small”)
> }
>
> // etc
> }
>
> l8r
> Sean
>
>
>
>> On Jan 9, 2017, at 11:54 AM, Tim Shadel via swift-evolution
>> <[email protected]> wrote:
>>
>> It seems like my original (simplified) examples didn't clearly highlight
>> what the problem I'm focused on solving.
>>
>> 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.
>>
>> Also, to be clear, my goal is _not_ code brevity. It is coherence, the
>> property where related code is located together. Some increase in code
>> verbosity is acceptable to make code more coherent, since that leads to
>> long-term maintainability.
>>
>> I've pulled out a few of the larger enums I've seen in code to try to
>> illustrate this. Along the way, I've made a few alterations based on the
>> comments I've seen come through. (Rien & Robert: I've pushed the definitions
>> inside the declaration, to ensure they don't end up in other files since
>> that was never my intention; Rien & Daniel: I've altered the syntax to open
>> a brace right after the case declaration making everything outside it the
>> default, and I like that better).
>>
>> Here's an enum used to consolidate the descriptions of UI elements for a
>> screen, allowing the datasource to alter their order, presentation, and
>> visibility while keeping the set of possible fields clean and finite. These
>> enum values are used much like singletons.
>>
>> enum OneOnOneField: Int {
>>
>>
>> case agenda
>> case summary
>> case date
>> case notes
>>
>> struct Info {
>>
>> var title
>> : String
>>
>> var placeholder
>> : String
>>
>> var image
>> :
>> UIImage
>>
>> }
>>
>>
>> var info
>> : Info {
>>
>>
>> switch self {
>>
>>
>> case .agenda:
>>
>>
>> return Info(
>>
>> title
>> : NSLocalizedString("Agenda", comment: "One on one field header"),
>>
>> placeholder
>> : NSLocalizedString("Add an agenda", comment: "One on one field
>> placeholder"),
>>
>> image
>> : #imageLiteral(resourceName: "Agenda-Small"))
>>
>>
>> case .summary:
>>
>>
>> return Info(
>>
>> title
>> : NSLocalizedString("Summary", comment: "One on one field header"),
>>
>> placeholder
>> : NSLocalizedString("Add a summary", comment: "One on one field
>> placeholder"),
>>
>> image
>> : #imageLiteral(resourceName: "Summary-Small"))
>>
>>
>> case .date:
>>
>>
>> return Info(title: "", placeholder: "", image: UIImage())
>>
>>
>> case .notes:
>>
>>
>> return Info(title: NSLocalizedString("Personal Notes", comment: "Title for
>> personal notes screen"), placeholder: "", image: UIImage())
>>
>>
>> }
>>
>>
>> }
>> }
>> Consolidating them could instead look something like this:
>>
>> enum OneOnOneField: Int {
>>
>>
>> var title
>> : String { return "" }
>>
>> var placeholder
>> : String { return "" }
>>
>> var image
>> : UIImage { return UIImage() }
>>
>>
>>
>> case agenda {
>> var title: String { return NSLocalizedString("Agenda", comment: "One
>> on one field header") }
>>
>> var placeholder
>> : String { return NSLocalizedString("Add an agenda", comment: "One on one
>> field placeholder") }
>>
>> var image
>> : UIImage { return #imageLiteral(resourceName: "Agenda-Small") }
>>
>>
>> }
>>
>>
>>
>> case summary {
>> var title: String { return NSLocalizedString("Summary", comment: "One
>> on one field header") }
>>
>> var placeholder
>> : String { return NSLocalizedString("Add a summary", comment: "One on one
>> field placeholder") }
>>
>> var image
>> : UIImage { return #imageLiteral(resourceName: "Summary-Small") }
>>
>>
>> }
>>
>>
>>
>> case date
>>
>> case notes {
>> var title: String { return NSLocalizedString("Personal Notes",
>> comment: "Title for personal notes screen") }
>>
>>
>> }
>>
>>
>>
>> }
>> Here's an enum that implements the basics of a state machine for OAuth 2
>> token use, refreshing, and login. Some of its cases have associated values
>> and some don't.
>>
>> enum TokenState: State {
>>
>>
>>
>> case loading
>> case none
>> case expired(Date)
>> case untested(token: String)
>> case validated(token: String)
>>
>> var description: String {
>>
>>
>> switch self {
>>
>>
>> case .loading:
>>
>>
>> return "Loading token from disk"
>>
>>
>> case .none:
>>
>>
>> return "No token found"
>>
>>
>> case let .expired(at):
>>
>>
>> return "Expired at \(at)"
>>
>>
>> case let .untested(token):
>>
>>
>> return "Received token \(token), but it hasn't been tested."
>>
>>
>> case let .validated(token):
>>
>>
>> return "Token \(token) has been validated."
>>
>>
>> }
>>
>>
>> }
>>
>>
>> mutating func react
>> (to event: Event) {
>>
>>
>> switch self {
>>
>>
>> case .loading:
>>
>>
>> switch event {
>>
>>
>> case _ as TokenNotFound:
>>
>> self
>> = .
>> none
>>
>> case let expired as TokenExpired:
>>
>> self
>> = .expired(expired.at)
>>
>>
>> case let loaded as TokenLoaded:
>>
>> self
>> = .untested(token: loaded.token)
>> default:
>>
>>
>> break
>>
>>
>> }
>>
>>
>> case .none:
>>
>>
>> switch event {
>>
>>
>> case let loggedIn as UserLoggedIn:
>>
>> self
>> = .untested(token: loggedIn.token)
>> default:
>>
>>
>> break
>>
>>
>> }
>>
>>
>> case .expired:
>>
>>
>> switch event {
>>
>>
>> case let refreshed as TokenRefreshed:
>>
>> self
>> = .untested(token: refreshed.token)
>>
>>
>> case _ as TokenRefreshErrored:
>>
>> self
>> = .
>> none
>>
>> default:
>>
>>
>> break
>>
>>
>> }
>>
>>
>> case let .untested(token):
>>
>>
>> switch event {
>>
>>
>> case _ as UserLoaded:
>>
>> self
>> = .validated(token: token)
>>
>>
>> case _ as TokenRejected:
>>
>> self
>> = .expired(at: Date())
>> default:
>>
>>
>> break
>>
>>
>> }
>>
>>
>> case .validated:
>>
>>
>> switch event {
>>
>>
>> case _ as TokenRejected:
>>
>> self
>> = .expired(Date())
>>
>>
>> case _ as UserLoggedOut:
>>
>> self
>> = .
>> none
>>
>> default:
>>
>>
>> break
>>
>>
>> }
>>
>>
>> }
>>
>>
>> }
>>
>>
>>
>> static var initialState: TokenState {
>>
>>
>> return .
>> loading
>>
>> }
>>
>>
>>
>> }
>> After consolidation, this becomes:
>>
>> enum TokenState: State {
>>
>>
>>
>> static var initialState: TokenState {
>>
>>
>> return .
>> loading
>>
>> }
>>
>>
>>
>> case loading {
>> var description: String {
>>
>>
>> return "Loading token from disk"
>>
>>
>> }
>>
>>
>> mutating func react
>> (to event: Event) {
>>
>>
>> switch event {
>>
>>
>> case _ as TokenNotFound:
>>
>> self
>> = .
>> none
>>
>> case let expired as TokenExpired:
>>
>> self
>> = .expired(at: expired.at)
>>
>>
>> case let loaded as TokenLoaded:
>>
>> self
>> = .untested(token: loaded.token)
>> default:
>>
>>
>> break
>>
>>
>> }
>>
>>
>> }
>>
>>
>> }
>>
>>
>>
>> case none {
>> var description: String {
>>
>>
>> return "No token found"
>>
>>
>> }
>>
>>
>> mutating func react
>> (to event: Event) {
>>
>>
>> switch event {
>>
>>
>> case let loggedIn as UserLoggedIn:
>>
>> self
>> = .untested(token: loggedIn.token)
>> default:
>>
>>
>> break
>>
>>
>> }
>>
>>
>> }
>>
>>
>> }
>>
>>
>>
>> case expired(at: Date) {
>> var description: String {
>>
>>
>> return "Expired at \(at)"
>>
>>
>> }
>>
>>
>> mutating func react
>> (to event: Event) {
>>
>>
>> switch event {
>>
>>
>> case let refreshed as TokenRefreshed:
>>
>> self
>> = .untested(token: refreshed.token)
>>
>>
>> case _ as TokenRefreshErrored:
>>
>> self
>> = .
>> none
>>
>> default:
>>
>>
>> break
>>
>>
>> }
>>
>>
>> }
>>
>>
>> }
>>
>>
>>
>> case untested(token: String) {
>> var description: String {
>>
>>
>> return "Received token \(token), but it hasn't been tested."
>>
>>
>> }
>>
>>
>> mutating func react
>> (to event: Event) {
>>
>>
>> switch event {
>>
>>
>> case _ as UserLoaded:
>>
>> self
>> = .validated(token: token)
>>
>>
>> case _ as TokenRejected:
>>
>> self
>> = .expired(at: Date())
>> default:
>>
>>
>> break
>>
>>
>> }
>>
>>
>> }
>>
>>
>> }
>>
>>
>>
>> case validated(token: String) {
>> var description: String {
>>
>>
>> return "Token \(token) has been validated."
>>
>>
>> }
>>
>>
>> mutating func react
>> (to event: Event) {
>>
>>
>> switch event {
>>
>>
>> case _ as TokenRejected:
>>
>> self
>> = .expired(at: Date())
>>
>>
>> case _ as UserLoggedOut:
>>
>> self
>> = .
>> none
>>
>> default:
>>
>>
>> break
>>
>>
>> }
>>
>>
>> }
>>
>>
>> }
>>
>>
>>
>> }
>>
>>
>>
>>> On Jan 8, 2017, at 12:22 PM, Derrick Ho via swift-evolution
>>> <[email protected]> wrote:
>>>
>>> Currently we can write a helper method to aid in getting the values inside
>>> the enum associated value. Below is a fully working implementation:
>>>
>>> ```
>>> enum Package {
>>> case box(String, Int)
>>> case circular(String)
>>>
>>> var associated: Associated {
>>> return Associated(package: self)
>>> }
>>>
>>> struct Associated {
>>> let box: (String, Int)?
>>> let circular: (String)?
>>> init(package: Package) {
>>> switch package {
>>> case .box(let b):
>>> box = b
>>> circular = nil
>>> case .circular(let b):
>>> box = nil
>>> circular = b
>>> }
>>> }
>>> }
>>> }
>>>
>>> let b = Package.box("square", 5)
>>> b.associated.box?.0 // Optional("square")
>>> b.associated.box?.1 // Optional(5)
>>> b.associated.circular // nil
>>>
>>> let c = Package.circular("round")
>>> c.associated.box?.0 // nil
>>> c.associated.box?.1 // nil
>>> c.associated.circular // Optional("round")
>>> ```
>>>
>>> I had to wedge in a special type called "Associated" and had to write some
>>> boiler-plate code to get this effect. It is quite predictable and can
>>> probably be done under the hood. I would of course prefer syntactic sugar
>>> to simplify it and turn
>>> ```
>>> b.associated.box?.0
>>> ```
>>> into
>>> ```
>>> b.box?.0
>>> ```
>>>
>>> On Sun, Jan 8, 2017 at 1:05 PM David Sweeris via swift-evolution
>>> <[email protected]> wrote:
>>>
>>> On Jan 8, 2017, at 06:53, Karim Nassar via swift-evolution
>>> <[email protected]> wrote:
>>>
>>>> One area of enums that I’d love to see some sugar wrapped around (and
>>>> perhaps this has already been discussed previously?) is extracting
>>>> associated values.
>>>>
>>>> There are many times where, given an enum like:
>>>>
>>>> enum Feedback {
>>>> case ok
>>>> case info(String)
>>>> case warning(String, Location)
>>>> case error(String, Location)
>>>> }
>>>>
>>>> I’d love it if we could tag the associated values with some semantic
>>>> accessor, perhaps borrowed from tuples:
>>>>
>>>> enum Feedback {
>>>> case ok
>>>> case info(msg: String)
>>>> case warning(msg: String, loc: Location)
>>>> case error(msg: String, loc: Location)
>>>> }
>>>>
>>>> then:
>>>>
>>>> let foo = self.getSomeFeedback() // -> Feedback
>>>> if let msg = foo.msg { // since not all cases can hold a ‘msg’ .msg is an
>>>> Optional
>>>> print(foo)
>>>> }
>>>
>>> Can't remember if it's come up before, but +1. I can't count how many times
>>> I've written something like:
>>> enum Foo : CustomStringConvertible {
>>> case c1(T1)
>>> case c2(T2)
>>> ...
>>> case cN(TN)
>>>
>>> var description: String {
>>> switch self {
>>> case .c1(let val): return "\(val)"
>>> case .c2(let val): return "\(val)"
>>> ...
>>> case .cN(let val): return "\(val)"
>>> }
>>> }
>>> }
>>>
>>> Being able to simplify that to:
>>> var description: String {
>>> let nilDesc = "some appropriate description"
>>> return "\(self.0 ?? nilDesc)"
>>> }
>>>
>>> Would be great.
>>>
>>> - Dave Sweeris
>>> _______________________________________________
>>> swift-evolution mailing list
>>> [email protected]
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> _______________________________________________
>>> swift-evolution mailing list
>>> [email protected]
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>> _______________________________________________
>> swift-evolution mailing list
>> [email protected]
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution