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

Reply via email to