[swift-evolution] [Proposal] Enums with stored properties

Mateusz Malczak mateusz at malczak.info
Wed Oct 12 17:51:45 CDT 2016


> RawRep is two things: a failable initialiser, and a getter.
> When the compiler synthesises them, it generates those
> exact same switch statements I proposed before. It’s quite
> simple, and there are potential issues to allowing arbitrary
> structs to use the same system. For one thing, they don’t all
> have a 1:1 equivalence relationship, so it’s possible for multiple
> enum cases to match the same raw struct. In that case, the order in
> which the compiler synthesises its checks is important, but it’s quite
> a hidden implementation detail that you wouldn’t be able to debug easily.
> It’s better to ask the user to implement the RawRep requirement
> themselves in those cases; it’s not a lot of work.

I'm aware of what is a basic idea behind RawRep. But it seems that I had a
wrong understanding of how rawValue's are managed. I was not aware of
the fact, that rawValue is created 'on demand' every time you use
.rawValue property on enum case. What it means, is that if you are
using a struct as a rawValue it is recreated from a string every
single time you are accessing rawValue. Not cool :)

Taking that into account I would consider changing the approach to
somethin more like
(http://swiftlang.ng.bluemix.net/#/repl/57febc248ef62b25bcea2a8a)
enum Format {

    struct FormatStruct {
        var width: Int = 0
        var height: Int = 0

        private init(width: Int, height: Int) {
            print("GENERATE")
            self.width = width
            self.height = height
        }

        internal static let SMALL = FormatStruct(width: 30, height: 30)

        internal static let MEDIUM = FormatStruct(width: 60, height: 60)

        internal static let LARGE = FormatStruct(width: 120, height: 120)

    }

    case SMALL
    case MEDIUM
    case LARGE

    var size: FormatStruct {
        switch self {
        case .SMALL:
            return Format.FormatStruct.SMALL
        case .MEDIUM:
            return Format.FormatStruct.MEDIUM
        case .LARGE:
            return Format.FormatStruct.LARGE
        }
    }
    var width: Int {
        return size.width
    }
    var height: Int {
        return size.height
    }
}

I have to admit I was not expecting that. I thought, enum case
rawValues are managed similar to statics and only referred to by enum
cases. That was also a reason why I was convinced what 'storing'
values in properties could be achieved as a variation of rawValue.

Taking all of that into account I could agree that this proposal is more a
syntax update than a new language feature. Storing additional
properties would mean
1. store values as a part of enum case = extending required memory
(like it is done in case of associated values)
2. force compiler to generate a rawValue struct implementing
RawRepresentable protocol

> What I meant by that is that you want convenience accessors for extra stuff you want to put in the enum’s payload (where the associated values are).
> I don’t think those values belong in the enum’s payload at all.

I never wanted to put anything in enum payload.

> What you propose looks dangerously close to the syntax for associated values, IMO. A cleaner version might look like (strawman syntax):

No. It does not look event similar to what you described.

--
| Mateusz Malczak
+-------------------------------



2016-10-12 21:20 GMT+02:00 Karl <razielim at gmail.com>:
>
>> On 12 Oct 2016, at 18:44, Mateusz Malczak <mateusz at malczak.info> wrote:
>>
>> I got a bit confused, I think we are here talking about completely
>> different features. Lets take a look at example from Karl
>>
>>> enum Something {
>>>   case oneThing(UIView)
>>>   case anotherThing(Error)
>>>   case yetAnotherThing(Int)
>>> }
>>>
>>> …which is dangerously similar to Braeden’s example, really does store an instance of UIView or Error or Int along with it. The size of the value is the maximum of those, plus a couple of bits > to record which case it is and what the type of the payload is.
>>
>> This is something we already have - and example of enum with
>> associated values attached to enum cases. It has nothing to do with
>> this proposal.
>
> Yes, I know what it shows. I’m saying that your proposed syntax looks dangerously close to what we already have (IMO), but means something entirely different.
>
>>
>> The proposal is about being able to add stored immutable properties to enum type
>> (not to enum cases as it is in case of associated values). Just like you can
>> store properties in structs.
>>
>
> If the properties are:
>  - immutable, and
>  - tied to specific enum cases
>
> then there is no need for them to be stored. You are asking to produce a hard-coded value based on the value of the enum - basically to wrap a switch statement.
> We don’t need to store copies of numbers that are already baked in to your source code.
>
>>> I think he wants convenience accessors for the associated values; the
>>> problem is that in his example, he shouldn’t even be using associated values
>>> at all.
>>
>> No, you are wrong, as I wrote its not even about associated values nor
>> about accessors.
>>
>
> What I meant by that is that you want convenience accessors for extra stuff you want to put in the enum’s payload (where the associated values are). I don’t think those values belong in the enum’s payload at all.
>
>>> You can already use structs as raw types. You just have to implement the RawRepresentable conformance yourself.
>>
>> I know. And this has been also already discussed. Please see a
>> previously discussed example here:
>> https://swiftlang.ng.bluemix.net/#/repl/57fbf08a4f9bcf25fdd41634
>>
>> And one more example with UIView stored in enum cases
>> http://swiftlang.ng.bluemix.net/#/repl/57fe67ad4bb9da26d5438387
>>
>> The only problem here is an amount of boilerplate code you need to
>> create and limitations caused by using ExpressibleBy(*)Literal.
>>
>
> RawRep is two things: a failable initialiser, and a getter.
> When the compiler synthesises them, it generates those exact same switch statements I proposed before. It’s quite simple, and there are potential issues to allowing arbitrary structs to use the same system. For one thing, they don’t all have a 1:1 equivalence relationship, so it’s possible for multiple enum cases to match the same raw struct. In that case, the order in which the compiler synthesises its checks is important, but it’s quite a hidden implementation detail that you wouldn’t be able to debug easily. It’s better to ask the user to implement the RawRep requirement themselves in those cases; it’s not a lot of work.
>
> Most of the time, when you’re talking about things more complex than a number or string, you only care about going from enum -> value. It’s much rarer that you want to take a complex struct and condense it down in to a single enum case.
>
>>> I agree with Karl on this. There should be clarity here as to what's proposed.
>>> If it's a change to how enums are laid out in memory, then you'll need to
>>> show we're not sacrificing performance/size in the overwhelmingly more
>>> common use cases, and why the extra storage is useful in the first place;
>>> if it's syntactic sugar, that has already been proposed multiple times and
>>> really isn't in scope for this phase of Swift 4--nor does it really enable any new use cases not possible now.
>>
>> I agree when it comes to memory and performance, but at the same time
>> we do not have to focus on how it should be laid out in memory. This
>> is already solved. It is now possible to create enumeration type with
>> custom structs as a rawType (see examples above). Enumeration type with
>> stored properties could be handled in exactly the same way. Taking
>> this into account I don't see any danger in terms of memory or
>> performance hits.
>>
>> In my opinion it's also not a syntactic sugar.
>>
>> Lets get back to the previously discussed example
>>
>> We are discussing here a enumeration type defined as
>> enum RectSize
>> {
>>   let height:Int
>>   let width:Int
>>   case small(width: 30, height: 30)
>>   case medium(width: 60, height: 60)
>>   case large(width: 120, height: 120)
>> }
>> where
>> var size: RectSize = .small
>> size.height == 30 // true
>> size.rawValue // Error:  RectSizes has no property `rawValue`.
>> size.height = 40 // Error:  `height` is immutable
>> print(size.height) // output: 30
>>
>>
>> There are at least two ways of this could be done.
>>
>> First solution would be to implement this as a new feature next to
>> already existing two (enum with associated values, enum with
>> rawValue). I don't have any detailed implementation in head.
>
> As I explained, "enum with rawValue” isn’t a feature. It’s a computed property - either one you implement yourself, or synthesised by the compiler for a couple of basic cases.
> What you want is a computed property, but you want to write the values near the cases instead of in a switch statement because they’re ugly. The compiler will have to use those labels and synthesise the switch statement for you, like it does for RawRep.
>
> What you propose looks dangerously close to the syntax for associated values, IMO. A cleaner version might look like (strawman syntax):
>
> enum RectSize {
>   @property(width, 30)
>   @property(height, 30)
>   case small
>
>   @property(width, 60)
>   @property(height, 60)
>   case medium
> }
>
> But that looks even uglier than having a switch statement. So then you think, what about if you could move those declarations elsewhere? maybe group them to avoid redundant typing?
>
> enum RectSize {
>    case small
>    case medium
>
>   @property(width: Int)
>       small = 30
>       medium = 60
>   @endproperty
>
>    ..etc
> }
>
> and presto! You have basically reinvented the switch statement in a computed property.
>
>>
>> Second approach would be to use struct as a rawValue. This feature is
>> already available.
>>
>> struct UnderlyingRectSizeEnumStruct {
>>    var width: Int
>>    var height: Int
>>    init(width: width, height: height) {
>>        self.width = width
>>        self.height = height
>>    }
>> }
>>
>> enum RectSize: UnderlyingRectSizeEnumStruct
>> {
>>   case small(width: 30, height: 30)
>>   case medium(width: 60, height: 60)
>>   case large(width: 120, height: 120)
>> }
>>
>>
>> I hope this explains what was the initial idea behind this proposal.
>>
>> --
>> | Mateusz Malczak
>> +-------------------------------
>>
>> 2016-10-12 17:07 GMT+02:00 Karl <razielim at gmail.com>:
>>> Not at all - his proposal looks like the enum cases have an associated value, when that is exactly what you _don’t_ want. It’s wasted storage because they have a handful of known values.
>>>
>>> It’s a PITA, I get it, I go through it all the time, too; but really this is the very definition of a computed property. There is nothing to be stored here. Meanwhile, something like:
>>>
>>> enum Something {
>>>   case oneThing(UIView)
>>>   case anotherThing(Error)
>>>   case yetAnotherThing(Int)
>>> }
>>>
>>> …which is dangerously similar to Braeden’s example, really does store an instance of UIView or Error or Int along with it. The size of the value is the maximum of those, plus a couple of bits to record which case it is and what the type of the payload is.
>>>
>>> Confusing those things just because you don’t like writing switch statements would be bad, IMO. It’s not that much code, and once you’ve done a few of them you can make it quite compact. If you have a boatload of associated values, wrap them in a struct.
>>> Some more convenient generated accessors for the associated data might be nice, but that’s been proposed and discussed to death. Others who have followed the lists more closely can maybe tell you why we don’t have an accepted proposal for it.
>>>
>>> - Karl
>>>
>>>> On 12 Oct 2016, at 13:52, Rien <Rien at Balancingrock.nl> wrote:
>>>>
>>>> I read Braeden’s example such that this proposal is in reality “just” syntactic sugar. AFAIAC it does not change how enums are implemented after compilation.
>>>>
>>>> Like sugar, it makes working with enums clearer and therefore easier. All initialisation values are defined right there with the ‘case’ instead of hidden in a tree of multiple ‘var’s.
>>>> While I was sceptical about this proposal, Braeden’s example makes it a +1.
>>>>
>>>> Rien.
>>>>
>>>> Btw: I made the almost identical suggestion you did ;-)
>>>>
>>>>
>>>>> On 12 Oct 2016, at 13:31, Karl <razielim at gmail.com> wrote:
>>>>>
>>>>> I very much disagree with the proposal, and all of the things supporting it (like deriving enums from other types and whatnot). I think you need to take a step up from caring about whether it’s a struct or enum and think about what you are trying to model; that will guide you towards the correct type(s) to use.
>>>>>
>>>>> You have only shown a handful of fixed size values, so I would suggest a computed property in your case:
>>>>>
>>>>> enum FixedSize {
>>>>> case small
>>>>> case medium
>>>>> case large
>>>>>
>>>>> struct Size { let width : Int; let height: Int }
>>>>>
>>>>> var size : Size {
>>>>>  switch self {
>>>>>      case .small: return Size(width: 30, height: 30)
>>>>>      // … etc
>>>>>  }
>>>>> }
>>>>> }
>>>>>
>>>>> There is no need for these sizes to be stored at all. If you want them baked in to your enum’s values, clearly you expect them to be specific values. It’s more efficient to just drop the stored data altogether in this case; this enum will get lowered in to single byte, which is more efficient to store and transport.
>>>>>
>>>>> Karl
>>>>>
>>>>>> On 12 Oct 2016, at 13:15, Mateusz Malczak via swift-evolution <swift-evolution at swift.org> wrote:
>>>>>>
>>>>>>> Mateusz, you lost me with “store some extra data”.
>>>>>>> Does that mean something extra besides the code that Braeden suggested?
>>>>>>
>>>>>> What I meant by “store some extra data” was, to be able to define
>>>>>> immutable properties of any type, as a part of enum instead of
>>>>>> defining getters witch switches. I think Braeden example explains the
>>>>>> whole idea of that proposal.
>>>>>>
>>>>>> --
>>>>>> | Mateusz Malczak
>>>>>>
>>>>>>
>>>>>> 2016-10-12 8:42 GMT+02:00 Rien <Rien at balancingrock.nl>:
>>>>>>> I’d give a +1 for the suggestion of Braeden.
>>>>>>>
>>>>>>> Mateusz, you lost me with “store some extra data”.
>>>>>>> Does that mean something extra besides the code that Braeden suggested?
>>>>>>>
>>>>>>> Rien.
>>>>>>>
>>>>>>>> On 12 Oct 2016, at 00:13, Mateusz Malczak via swift-evolution <swift-evolution at swift.org> wrote:
>>>>>>>>
>>>>>>>> That's exactly what this proposal is about. I would like to
>>>>>>>> keep all enum properties but add an extra feature, so that enums can
>>>>>>>> store some extra data.
>>>>>>>> --
>>>>>>>> | Mateusz Malczak
>>>>>>>> +-------------------------------
>>>>>>>> | mateusz at malczak.info
>>>>>>>> | http://malczak.info
>>>>>>>>
>>>>>>>>
>>>>>>>> 2016-10-11 23:42 GMT+02:00 Braeden Profile <jhaezhyr12 at gmail.com>:
>>>>>>>>> So, just to recap, the proposed solution is to help enums expose associated
>>>>>>>>> values via properties, and is not to create enums that are open to extra
>>>>>>>>> unnamed cases (RectSize(width:0,height:10))?  What I see is that enums would
>>>>>>>>> still maintain their standing where an instance is just a selection of a
>>>>>>>>> finite number of options, possibly with data attached.  In proposal 1, we
>>>>>>>>> want some sort of syntax where this…
>>>>>>>>>
>>>>>>>>> enum RectSize
>>>>>>>>> {
>>>>>>>>> let height:Int
>>>>>>>>> let width:Int
>>>>>>>>> case small(width: 30, height: 30)
>>>>>>>>> case medium(width: 60, height: 60)
>>>>>>>>> case large(width: 120, height: 120)
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> …is syntactically just like writing this…
>>>>>>>>>
>>>>>>>>> enum RectSize
>>>>>>>>> {
>>>>>>>>> case small
>>>>>>>>> case medium
>>>>>>>>> case large
>>>>>>>>> var height:Int
>>>>>>>>> {
>>>>>>>>>  switch self
>>>>>>>>>  {
>>>>>>>>>     case .small: return 30
>>>>>>>>>     case .medium: return 60
>>>>>>>>>     case .large: return 90
>>>>>>>>>  }
>>>>>>>>> }
>>>>>>>>> let width:Int
>>>>>>>>> {
>>>>>>>>>  switch self
>>>>>>>>>  {
>>>>>>>>>     case .small: return 30
>>>>>>>>>     case .medium: return 60
>>>>>>>>>     case .large: return 90
>>>>>>>>>  }
>>>>>>>>> }
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> …right?  That way, you can write this:
>>>>>>>>>
>>>>>>>>> var size: RectSize = .small
>>>>>>>>> size.height == 30 // true
>>>>>>>>> size.rawValue // Error:  RectSizes has no property `rawValue`.
>>>>>>>>> size.height = 40 // Error:  `height` is immutable
>>>>>>>>> size = .medium
>>>>>>>>>
>>>>>>>>> I think we were also (separately) proposing to extend `rawValue` to take all
>>>>>>>>> kinds of statically known values, like structs or tuples.  Doing that would
>>>>>>>>> accomplish much of the same thing.
>>>>>>>>>
>>>>>>>>> Someone fact-check me here!  I really do think something like this would be
>>>>>>>>> a good idea, if we could get the right syntax.
>>>>>>>>>
>>>>>>>>> On Oct 11, 2016, at 7:06 AM, Mateusz Malczak via swift-evolution
>>>>>>>>> <swift-evolution at swift.org> wrote:
>>>>>>>>>
>>>>>>>>> Hi,
>>>>>>>>> I think we are here discussing two different aspects of introducing
>>>>>>>>> this new feature - code syntax and underlying implementation.
>>>>>>>>> In terms of code syntax I would go with first proposal as it seems to
>>>>>>>>> me the simplest approach. When it comes to underlying implementation,
>>>>>>>>> I can imagine that during compilation internal struct is created, as
>>>>>>>>> well as any required property getters. This way you could get a
>>>>>>>>> variation of rawValue implementation, at least from theoretical point
>>>>>>>>> of view :D
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> | Mateusz Malczak
>>>>>>>>> +-------------------------------
>>>>>>>>> | mateusz at malczak.info
>>>>>>>>> | http://malczak.info
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> 2016-10-10 23:42 GMT+02:00 Haravikk <swift-evolution at haravikk.me>:
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> On 10 Oct 2016, at 20:34, Mateusz Malczak <mateusz at malczak.info> wrote:
>>>>>>>>>
>>>>>>>>> I know, but what I'm saying is that this problem could be solved in the
>>>>>>>>> multiple values case by allowing tuples as raw values for enums, since that
>>>>>>>>> would allow you to specify both width and height. So it'd look something
>>>>>>>>> like this:
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> We have three different possible solution
>>>>>>>>> 1. stored properties defined as part of enumeration type
>>>>>>>>> enum RectSizes: MyRect
>>>>>>>>> {
>>>>>>>>> let height:Int
>>>>>>>>> let width:Int
>>>>>>>>> case Small(width: 30, height: 30)
>>>>>>>>> case Medium(width: 60, height: 60)
>>>>>>>>> case Large(width: 120, height: 120)
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> 2. struct as rawValue
>>>>>>>>> struct MyRect
>>>>>>>>> {
>>>>>>>>> var height:Int
>>>>>>>>> var width:Int
>>>>>>>>> var area:Int {return height:Int*width}
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> enum RectSizes: MyRect
>>>>>>>>> {
>>>>>>>>> case Small(30,30)
>>>>>>>>> case Medium(60,60)
>>>>>>>>> case Large(120,120)
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> 3. tuples as rawValue
>>>>>>>>> enum Format : (width:Int, height:Int) {
>>>>>>>>> case small(30, 30)
>>>>>>>>> case medium(60, 60)
>>>>>>>>> case large(120, 120)
>>>>>>>>>
>>>>>>>>> var width:Int { return self.rawValue.width }
>>>>>>>>> var height:Int { return self.rawValue.height }
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> Solutions 2 and 3 are quire similar, to get value of a stored property
>>>>>>>>> we need to use rawValue or define value getters. In addition in
>>>>>>>>> solution 2 we define an additional data type just to be used as an
>>>>>>>>> enumeration type rawValue type. In my opinion, first approach would be
>>>>>>>>> a best solution, type definition is clear and self-explanatory because
>>>>>>>>> it is similar to how enums/classes are defined.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> | Mateusz Malczak
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Actually I'd say your option 2 here is more similar to option 1 (you seem to
>>>>>>>>> be using a struct to define the stored properties instead). The issue here
>>>>>>>>> is that storing properties conflicts with what you're actually doing, which
>>>>>>>>> is storing case-specific values, which is what rawValue already does, it's
>>>>>>>>> just too limited for your current use-case (multiple values).
>>>>>>>>>
>>>>>>>>> The complete solution would be to introduce the concept of tuples as
>>>>>>>>> literals (even though they can't currently conform to types); this would
>>>>>>>>> make it a lot easier to support the use of any type as a fixed value for
>>>>>>>>> each case (not just tuples). For example, say we introduced as new protocol:
>>>>>>>>>
>>>>>>>>> protocol ExpressableByTuple {
>>>>>>>>> associatedtype TupleType // somehow force this to be a tuple or
>>>>>>>>> ExpressableByType type
>>>>>>>>> init(tupleLiteral:TupleType)
>>>>>>>>> }
>>>>>>>>>
>>>>>>>>> With a bit of magic all tuples could conform to this protocol with
>>>>>>>>> themselves as the literal type, allowing us to use them as enum raw values;
>>>>>>>>> likewise this could then be used to more easily enable any custom
>>>>>>>>> struct/class for storage in enum cases, as instead of supporting their
>>>>>>>>> constructors directly we can just support construction via a tuple literal.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> My other reason I don't favour option 1, while it looks a bit prettier, is
>>>>>>>>> that it's a bit confusing; enums have two types of stored properties, ones
>>>>>>>>> that can be changed (and inspected) which is what you get when you declare
>>>>>>>>> case small(Int, Int) for example, these are stored as part of the enum
>>>>>>>>> itself (so in that example it's 17-bytes on a 64-bit system). However
>>>>>>>>> rawValues are more like constants/static values, and don't increase the size
>>>>>>>>> of the type, and I just feel that this is the right way to do what you're
>>>>>>>>> proposing.
>>>>>>>>>
>>>>>>>>> _______________________________________________
>>>>>>>>> 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
>>>>>>>
>>>>>> _______________________________________________
>>>>>> swift-evolution mailing list
>>>>>> swift-evolution at swift.org
>>>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>>
>>>>
>>>
>


More information about the swift-evolution mailing list