Sent from my iPad
> On Jun 1, 2016, at 9:48 AM, David Waite via swift-evolution > <[email protected]> wrote: > > 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 This is pretty similar to what we were discussing last week in this thread: https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160523/018799.html I'm planning to write up a proposal when I have time (hopefully in the next week or so). > > -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 > > _______________________________________________ > 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
