One thing I did often in Java (and miss in Swift) is using their enums to build state machines or implement command patterns for common commands.
Java enums are a sealed set of subclasses of the enum base type with (hopefully) immutable, singleton instances. So you can do fun things like: - Define the base class constructor to be called to instantiate the subclasses, and declare the cases with the constructor arguments - Declare a method on the base type and refine it on 1-2 particular cases - Declare the enum implements an interface, and implement that interface separately for each case. - Define data accessors specific to the type (such as the planets example above) I like the SuitInfo approach below - with extensions, I think I can get close to what I have done in the past with Java. Maybe one day there is syntax to do this in the language directly -DW > On May 31, 2016, at 11:44 AM, Vladimir.S via swift-evolution > <[email protected]> wrote: > > I'm not sure about my opinion on this proposal, but I believe you should add > this as alternatives of how we can have the similar features today without > injecting stored properties into enums : > > enum Suit { > case spades > case hearts > case diamonds > case clubs > > struct SuitInfo { > let simpleDescription: String > let color: UIColor > let symbol: String > let bezierPath: UIBezierPath > } > > var info : SuitInfo { > switch self { > case .spades: > let path = UIBezierPath() > // omitted lines ... > > return SuitInfo( > simpleDescription: "spades", > color: .blackColor(), > symbol: "♠", > bezierPath: path) > > case .hearts: > let path = UIBezierPath() > // omitted lines ... > > return SuitInfo( > simpleDescription: "hearts", > color: .redColor(), > symbol: "♥", > bezierPath: path) > > case .diamonds: > let path = UIBezierPath() > // omitted lines ... > > return SuitInfo( > simpleDescription: "diamonds", > color: .redColor(), > symbol: "♦", > bezierPath: path) > > case .clubs: > let path = UIBezierPath() > // omitted lines ... > > return SuitInfo( > simpleDescription: "clubs", > color: .blackColor(), > symbol: "♣", > bezierPath: path) > > } > } > } > > and this: > > enum Suit { > case spades > case hearts > case diamonds > case clubs > > struct SuitInfo { > let simpleDescription: String > let color: UIColor > let symbol: String > let bezierPath: UIBezierPath > } > > static let spadesInfo : SuitInfo = { > let path = UIBezierPath() > // omitted lines ... > > return SuitInfo( > simpleDescription: "spades", > color: .blackColor(), > symbol: "♠", > bezierPath: path) > }() > > static let heartsInfo : SuitInfo = { > let path = UIBezierPath() > // omitted lines ... > > return SuitInfo( > simpleDescription: "hearts", > color: .redColor(), > symbol: "♥", > bezierPath: path) > }() > > static let diamondsInfo : SuitInfo = { > let path = UIBezierPath() > // omitted lines ... > > return SuitInfo( > simpleDescription: "diamonds", > color: .redColor(), > symbol: "♦", > bezierPath: path) > }() > > static let clubsInfo : SuitInfo = { > let path = UIBezierPath() > // omitted lines ... > > return SuitInfo( > simpleDescription: "clubs", > color: .blackColor(), > symbol: "♣", > bezierPath: path) > }() > > > var info : SuitInfo { > switch self { > case .spades: return Suit.spadesInfo > case .hearts: return Suit.heartsInfo > case .diamonds: return Suit.diamondsInfo > case .clubs: return Suit.clubsInfo > } > } > } > > > On 31.05.2016 17:17, Jānis Kiršteins via swift-evolution wrote: >> I wrote a proposal draft: >> >> # Enum case stored properties >> >> * Proposal: TBD >> * Author: [Janis Kirsteins](https://github.com/kirsteins) >> * Status: TBD >> * Review manager: TBD >> >> ## Introduction >> >> This proposal allows each enum case to have stored properties. >> >> ## Motivation >> >> Enums cases can have a lot of constant (or variable) static values >> associated with it. For example, planets can have mass, radius, age, >> closest star etc. Currently there is no way to set or get those values >> easily. >> >> Example below shows that is hard to read and manage static associated >> values with each case. It is hard to add or remove case as it would >> require to add or remove code in four different places in file. Also >> static associated value like `UIBezierPath` is recreated each time the >> property is computed while it's constant. >> >> ```swift >> enum Suit { >> case spades >> case hearts >> case diamonds >> case clubs >> >> var simpleDescription: String { >> switch self { >> case .spades: >> return "spades" >> case .hearts: >> return "hearts" >> case .diamonds: >> return "diamonds" >> case .clubs: >> return "clubs" >> } >> } >> >> var color: UIColor { >> switch self { >> case .spades: >> return .blackColor() >> case .hearts: >> return .redColor() >> case .diamonds: >> return .redColor() >> case .clubs: >> return .blackColor() >> } >> } >> >> var symbol: String { >> switch self { >> case .spades: >> return "♠" >> case .hearts: >> return "♥" >> case .diamonds: >> return "♦" >> case .clubs: >> return "♣" >> } >> } >> >> var bezierPath: UIBezierPath { >> switch self { >> case .spades: >> let path = UIBezierPath() >> // omitted lines ... >> return path >> case .hearts: >> let path = UIBezierPath() >> // omitted lines ... >> return path >> case .diamonds: >> let path = UIBezierPath() >> // omitted lines ... >> return path >> case .clubs: >> let path = UIBezierPath() >> // omitted lines ... >> return path >> } >> } >> } >> ``` >> >> ## Proposed solution >> >> Support stored properties for enum cases just as each case were an >> instance. Case properties are initialized block after each case >> declaration. >> >> ```swift >> enum Suit { >> let simpleDescription: String >> let color: UIColor >> let symbol: String >> let bezierPath: UIBezierPath >> >> case spades { >> simpleDescription = "spades" >> color = .blackColor() >> symbol = "♠" >> let bezierPath = UIBezierPath() >> // omitted lines ... >> self.bezierPath = bezierPath >> } >> >> case hearts { >> simpleDescription = "hearts" >> color = .redColor() >> symbol = "♥" >> let bezierPath = UIBezierPath() >> // omitted lines ... >> self.bezierPath = bezierPath >> } >> >> case diamonds { >> simpleDescription = "diamonds" >> color = .redColor() >> symbol = "♦" >> let bezierPath = UIBezierPath() >> // omitted lines ... >> self.bezierPath = bezierPath >> } >> >> case clubs { >> simpleDescription = "clubs" >> color = .blackColor() >> symbol = "♣" >> let bezierPath = UIBezierPath() >> // omitted lines ... >> self.bezierPath = bezierPath >> } >> } >> >> let symbol = Suit.spades.symbol // "♠" >> ``` >> >> The proposed solution improves: >> - Readability as cases are closer with their related data; >> - Improves code maintainability as a case can be removed or added in one >> place; >> - Improved performance as there is no need to recreate static values; >> - ~30% less lines of code in given example. >> >> ## Detailed design >> >> #### Stored properties >> >> Enum stored properties are supported the same way they are supported >> for structs can classes. Unlike enum associated values, stored >> properties are static to case and are shared for the same case. >> >> Properties are accessed: >> ```swift >> let simpleDescription = Suit.spades.simpleDescription >> ``` >> >> Mutable properties can be set: >> ```swift >> Suit.spades.simpleDescription = "new simple description" >> ``` >> >> #### Initialization >> >> If enum has uninitialized stored property it must be initialized in a >> block after each case declaration. The block work the same way as >> struct initialization. At the end of initialization block all >> properties must be initialized. >> >> ```swift >> enum Suit { >> var simpleDescription: String >> >> case spades { >> simpleDescription = "spades" >> } >> } >> ``` >> >> Initialization block can be combine with use of `rawValue`: >> >> ```swift >> enum Suit: Int { >> var simpleDescription: String >> >> case spades = 1 { >> simpleDescription = "spades" >> } >> } >> ``` >> or associated values of the case: >> >> ```swift >> enum Suit { >> var simpleDescription: String >> >> case spades(Int) { >> simpleDescription = "spades" >> } >> } >> ``` >> >> ## Impact on existing code >> >> Stored properties for enums are not currently not supported, so there >> is no impact on existing code. >> >> ## Alternatives considered >> >> - Use labeled tuple as `rawValue` of the enum case. This approach is >> not compatible as it conflicts with intention of `rawValue` of Swift >> enum; >> - Use per case initializer like [Java >> Enum](https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html). >> Swift enum uses custom initializer syntax to setup instances, not >> cases. So this approach is not suitable for Swift. >> >> >> On Sun, May 29, 2016 at 3:42 PM, Leonardo Pessoa <[email protected]> wrote: >>> I think that's the case with enums. You're changing their current behaviour >>> of only having stored values to one in which it's computed (even if only >>> once and then stored). Enums are IMO something that have a static value you >>> know beforehand and can count on. That's why I'm not fond of the accessor >>> proposal. Otherwise I think we're transforming enums into a closed set of >>> struct instances and one could do that already by using a private init. >>> >>> >>>> On 29 May 2016, at 3:38 am, Jānis Kiršteins via swift-evolution >>>> <[email protected]> wrote: >>>> >>>> I agree with the argument about use of "where", not replacing the raw >>>> value and having some kind of initialization block. But I cannot see >>>> why "accessors" concept is any better than stored properties to solve >>>> the particular problem. The "accessors" concept has much wider scope >>>> than enums and is a separate proposal. >>>> >>>> On Sat, May 28, 2016 at 11:39 PM, Brent Royal-Gordon >>>> <[email protected]> wrote: >>>>>>> - Abusing rawValue is just that: an abuse. >>>>>> >>>>>> My original proposal does not replace rawValue and is compatible with it. >>>>> >>>>> `rawValue` has a different purpose from how you're using it. It's >>>>> supposed to allow you to convert your type to some other *equivalent* >>>>> type, like an equivalent integer or string. Moreover, it's supposed to >>>>> allow you to *reconstruct* the instance from the raw value—remember, >>>>> `RawRepresentable` has an `init(rawValue:)` requirement. >>>>> >>>>> It is *not* supposed to be an ancillary bag of information on the side. >>>>> You're cramming a square peg into a round hole here. >>>>> >>>>> (Also, if you use `rawValue` for an ancillary bag of information, that >>>>> means you *can't* use it on the same type for its intended purpose. For >>>>> instance, you would not be able to assign numbers to your Planet enum's >>>>> cases to help you serialize them or bridge them to Objective-C. That's >>>>> not good.) >>>>> >>>>>>> - Using `where` just doesn't match the use of `where` elsewhere in the >>>>>>> language; everywhere else, it's some kind of condition. >>>>>> >>>>>> It is also used in generic type constraints. Plus it reads like human >>>>>> language: `case mercury where (mass: 3.303e+23, radius: 2.4397e6)` >>>>> >>>>> But a generic constraint is also a type of condition: it specifies types >>>>> which are permitted and divides them from types that are not. >>>>> >>>>> This is *not* a condition. It's not anything like a condition. It's >>>>> simply not consistent with anything else in the language. >>>>> >>>>>>> - Dictionaries are the most straightforward way to handle this with the >>>>>>> current language, but their lack of exhaustiveness checking is a >>>>>>> problem. >>>>>> >>>>>> Dictionaries can be used as workaround, but they cannot (lack of >>>>>> exhaustiveness) solve the problem. >>>>> >>>>> I agree that they're a halfway solution. >>>>> >>>>> If `ValuesEnumerable` were to be accepted (and to have a generic >>>>> requirement for its `allValues` property), you could write a >>>>> Dictionary-like type which ensured at initialization time that it was >>>>> exhaustive. That's not as good as compile time, but it's not bad—sort of >>>>> a three-quarters solution. >>>>> >>>>> struct ExhaustiveDictionary<Key: Hashable, Value where Key: >>>>> ValuesEnumerable>: Collection, DictionaryLiteralConvertible { >>>>> private var dictionary: [Key: Value] >>>>> >>>>> init(dictionaryLiteral elements: (Key, Value)...) { >>>>> dictionary = [:] >>>>> for (k, v) in elements { >>>>> dictionary[k] = v >>>>> } >>>>> >>>>> if dictionary.count != Key.allValues.count { >>>>> let missingKeys = Key.allValues.filter { >>>>> dictionary[$0] == nil } >>>>> preconditionFailure("ExhaustiveDictionary >>>>> is missing elements from \(Key.self): \(missingKeys)") >>>>> } >>>>> } >>>>> >>>>> var startIndex: Dictionary.Index { >>>>> return dictionary.startIndex >>>>> } >>>>> var endIndex: Dictionary.Index { >>>>> return dictionary.endIndex >>>>> } >>>>> subscript(index: Dictionary.Index) -> (Key, Value) { >>>>> return dictionary[index] >>>>> } >>>>> func index(after i: Dictionary.Index) -> Dictionary.Index { >>>>> return dictionary.index(after: i) >>>>> } >>>>> >>>>> subscript(key: Key) -> Value { >>>>> get { return dictionary[key]! } >>>>> set { dictionary[key] = newValue } >>>>> } >>>>> } >>>>> >>>>>>> What I would do is borrow the "accessors" concept from the property >>>>>>> behaviors proposal and extend it so that it supported both functions >>>>>>> and variables. >>>>>> >>>>>> Wouldn't accessor just be a redundant keyword here? Currently enums do >>>>>> not support stored properties, so I guess there is no extra need to >>>>>> mark properties with any special keyword. >>>>> >>>>> The keyword is mainly to indicate the unusual syntax at the definition >>>>> site, where you only have to specify the name of the accessor you're >>>>> defining, not a `func` or `var` keyword, a return type, or even parameter >>>>> names. (Like `willSet`, there's a default parameter name you can use.) >>>>> Secondarily, though, I think it's helpful to indicate very explicitly >>>>> that this is not an ordinary method or property definition, even if the >>>>> compiler could perhaps sort things out without it. `accessor` is >>>>> something a user can Google if they've never seen it before. >>>>> >>>>>> Property accessors might work for enums with associated values, but >>>>>> not so well without them. >>>>> >>>>> The two have nothing to do with each other. I showed your planets >>>>> example, which has no associated values but uses accessors just fine. >>>>> >>>>> -- >>>>> Brent Royal-Gordon >>>>> Architechies >>>>> >>>> _______________________________________________ >>>> 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
signature.asc
Description: Message signed with OpenPGP using GPGMail
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
