Working with enums by name

Indeed, you’re quite right: verified that I get “Mars” even when the enum is in a framework.

It took a little digging to get back what I was thinking of: it’s when the enum value is inside some other data structure that you get an annoyingly fully qualified name:

    enum CoinSide {
        case heads
        case tails
    }

    enum CoinState {
        case inAir
        case landed(showing: CoinSide)
    }

    print(CoinState.inAir) // → "inAir"

    // …but…

    print(CoinState.landed(showing: .heads)) // → "landed(CoinSide.heads)"

    print([CoinSide.heads: 1]) // → "[CoinSide.heads: 1]"

This is the case I was thinking of where the module name comes into play. Drop those enums into a framework, and you’ll get "landed(MyFramework.CoinSide.heads)". Ugh!

So what if you want those second two to print out as "landed(heads)" and "[heads: 1]”? This does not work:

    enum CoinSide: CustomStringConvertible {
        case heads
        case tails
        
        var description: String {
            return String(self) // infinite recursion
        }
    }

There’s no automatically implemented description (or debugDescription) property we can delegate to. The conversion of .heads → "heads" is apparently runtime magic that we lose access to as soon as we implement CustomStringConvertible or CustomDebugStringConvertible, and therefore AFAIK there's no way to do this other than switching on all the cases:

    enum CoinSide: CustomStringConvertible {
        case heads
        case tails
        
        var description: String {
            switch(self) {
                case heads: return "heads"
                case tails: return "tails"
            }
        }
    }

Is is true that there’s no better way? Is there some CustomVerboseDebugStringConvertible protocol we can override to change only the "MyFramework.CoinSide.heads" form?

If indeed there is no better way, it seems like a really good case for having the synthesized .caseName property. Even if there is a CustomVerboseDebugStringConvertible to override in the particular case above, being able to customize an enum’s description but still use the enum case name in that description seems like a compelling use case as well.

Cheers, P

···

On Jun 1, 2016, at 10:47 AM, Leonardo Pessoa <me@lmpessoa.com> wrote:

Paul, in all my tests for this thread printing the enum value only
produced the enum value's name ("Mars" in your example). The proposal
of having a .caseName (or should it better be .caseValue to cover
enums with associated values? any other suggestions?) will prevent
that changes to this behaviour crash apps in the future as this should
always produce the same result even if the string representation
changes.

L

On 1 June 2016 at 12:15, Paul Cantrell via swift-evolution > <swift-evolution@swift.org> wrote:

IIRC, string interpolation prepends the module name if the enum belongs to a module: “MyLib.Mars” instead of just “Mars”. It’s also been a source of compiler crashes, at least in the past.

Those two factors forced me into this ugliness: https://github.com/bustoutsolutions/siesta/blob/master/Source/ResourceObserver.swift#L106-L115

A clean, documented, supported way of exposing the enum case name that the runtime clearly already has available seems sensible — and should be independent of the raw type.

Cheers, P

On Jun 1, 2016, at 5:10 AM, Charlie Monroe via swift-evolution <swift-evolution@swift.org> wrote:

This is, however, kind of a hack IMHO that relies on the compiler behavior that isn't well documented.

For example, this:

enum Planet {
     case Earth
     case Mars
}

"\(Planet.Mars)" // This is "Mars"

Works as well. You don't need to have the represented value to be String.

Note that this:

- works both when you have a plain enum, or enum Planet: Int, or whatever raw value kind
- does not work (!) when declared as @objc - then the result is "Planet".

On Jun 1, 2016, at 9:52 AM, Patrick Smith via swift-evolution <swift-evolution@swift.org> wrote:

I had no idea you could do this!!

On 1 Jun 2016, at 12:32 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Who said anything about repeating the name?

Welcome to Apple Swift version 2.2 (swiftlang-703.0.18.8 clang-703.0.30). Type :help for assistance.
1> enum Planet: String { case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune }
2> Planet.mercury.rawValue
$R0: String = "mercury"

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Try this:

enum Size: String { case Fit, Fill }
print(Size.Fit.rawValue)

···

On 01.06.2016 13:42, Leonardo Pessoa via swift-evolution wrote:

Just a fix. I've just tried the following code and the compiler complained
there is no .rawValue on the type.

> enum Size { case Fit, Fill }
> print(Size.Fit.rawValue)

Then, as I said before, you can only get the value name as a string from
interpolation and need to do everything by hand the other way around.

L
---------------------------------------------------------------------------
From: Charlie Monroe via swift-evolution <mailto:swift-evolution@swift.org>
Sent: ‎01/‎06/‎2016 07:19 AM
To: Brent Royal-Gordon <mailto:brent@architechies.com>
Cc: Swift-evolution <mailto:swift-evolution@swift.org>
Subject: Re: [swift-evolution] Working with enums by name

Sorry, must've missed that.

On Jun 1, 2016, at 12:17 PM, Brent Royal-Gordon <brent@architechies.com> > wrote:

This is, however, kind of a hack IMHO that relies on the compiler

behavior that isn't well documented.

It's documented in "The Swift Programming Language", in the same

paragraphs where the `enum Planet` example we've been working with comes from.

“When you’re working with enumerations that store integer or string raw

values, you don’t have to explicitly assign a raw value for each case. When
you don’t, Swift will automatically assign the values for you.

<snip>
“When strings are used for raw values, the implicit value for each case

is the text of that case’s name.”

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

This should work but feels like an ugly hack to me. What if I needed
the enum like this?

> enum Size : Double {
> case Fit = 0.5
> case Fill = 3.0
> }

What if you needed both Int and Double rawValues? What if you needed rawValues that were cryptographically signed? We have to decide which use cases are common enough to support directly in the language, and I'm not convinced that "I need to look cases up by name, but I have no choice but to use rawValue for something else" is one of them—that is, that it's *so* common that we need to direct our scarce engineering resources towards designing and implementing a separate feature merely to accommodate it. There are a whole lot of things that are *way* higher on our to-do list than this, arguably including metaprogramming features which would let you write this yourself instead of sticking it in the core language.

···

--
Brent Royal-Gordon
Architechies

Oops. I have wanted this feature and I have created `name` methods for non-String enums numerous times (whether I had to or not, I guess). If there is a formal proposal for this, I could not find it. I am obviously not familiar with the entire proposal but this is a feature that I have wanted almost from the first day I started using Swift.

I would prefer a `name`method for enums and not overload `description`.

A standard way of accessing the unique names of enum values would be compatible with a future variant of `enum` that would allow non-unique rawValue should Swift ever support that feature of C/C++ enums. (hint) It would become requirement for these enums, I think.

Accessing the names of enum values is extremely useful for diagnostics and quick and dirty UI e.g. playgrounds.

···

On Jun 1, 2016, at 3:07 PM, Paul Cantrell <cantrell@pobox.com> wrote:

On Jun 1, 2016, at 2:20 PM, Christopher Kornher via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jun 1, 2016, at 12:53 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This is the case I was thinking of where the module name comes into play. Drop those enums into a framework, and you’ll get "landed(MyFramework.CoinSide.heads)". Ugh!

This seems to be more of namespace “import” issue than a problem with enums specifically. Declaring enums within another entity is a useful. I take advantage of qualified naming to make short, possibly non-unique enum names.

Yes, in _code_ fully qualified names are useful.

This thread is the need for unqualified names in _strings_ — for passing to external systems, logging, etc.

The problematic behavior I was pointing out is that enums get converted to a name-only string when they are at the _top_ level of a data structure, but a fully qualified Module.EnumType.caseName when they're _nested_ inside a collection or another enum. AFAIK, there's no way to override this behavior without manually coding a case statement to map enum values to strings. This proposal would solve that.

Cheers, P

Yes, in _code_ fully qualified names are useful.

This thread is the need for unqualified names in _strings_ — for passing to external systems, logging, etc.

The problematic behavior I was pointing out is that enums get converted to a name-only string when they are at the _top_ level of a data structure, but a fully qualified Module.EnumType.caseName when they're _nested_ inside a collection or another enum. AFAIK, there's no way to override this behavior without manually coding a case statement to map enum values to strings. This proposal would solve that.

Cheers, P

···

On Jun 1, 2016, at 2:20 PM, Christopher Kornher via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jun 1, 2016, at 12:53 PM, Paul Cantrell via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This is the case I was thinking of where the module name comes into play. Drop those enums into a framework, and you’ll get "landed(MyFramework.CoinSide.heads)". Ugh!

This seems to be more of namespace “import” issue than a problem with enums specifically. Declaring enums within another entity is a useful. I take advantage of qualified naming to make short, possibly non-unique enum names.

Enums outside frameworks will still rely on you as the programmer to
know to which enum the string representation belongs to (it does so
for the raw values) so I see no reason why the .caseName result should
have the name of any underlying type the case belongs to.

L

···

On 1 June 2016 at 16:20, Christopher Kornher via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 1, 2016, at 12:53 PM, Paul Cantrell via swift-evolution > <swift-evolution@swift.org> wrote:

Indeed, you’re quite right: verified that I get “Mars” even when the enum is
in a framework.

It took a little digging to get back what I was thinking of: it’s when the
enum value is inside some other data structure that you get an annoyingly
fully qualified name:

    enum CoinSide {
        case heads
        case tails
    }

    enum CoinState {
        case inAir
        case landed(showing: CoinSide)
    }

    print(CoinState.inAir) // → "inAir"

    // …but…

    print(CoinState.landed(showing: .heads)) // → "landed(CoinSide.heads)"

    print([CoinSide.heads: 1]) // → "[CoinSide.heads: 1]"

This is the case I was thinking of where the module name comes into play.
Drop those enums into a framework, and you’ll get
"landed(MyFramework.CoinSide.heads)". Ugh!

This seems to be more of namespace “import” issue than a problem with enums
specifically. Declaring enums within another entity is a useful. I take
advantage of qualified naming to make short, possibly non-unique enum names.

So what if you want those second two to print out as "landed(heads)" and
"[heads: 1]”? This does not work:

    enum CoinSide: CustomStringConvertible {
        case heads
        case tails

        var description: String {
            return String(self) // infinite recursion
        }
    }

There’s no automatically implemented description (or debugDescription)
property we can delegate to. The conversion of .heads → "heads" is
apparently runtime magic that we lose access to as soon as we implement
CustomStringConvertible or CustomDebugStringConvertible, and therefore AFAIK
there's no way to do this other than switching on all the cases:

    enum CoinSide: CustomStringConvertible {
        case heads
        case tails

        var description: String {
            switch(self) {
                case heads: return "heads"
                case tails: return "tails"
            }
        }
    }

Is is true that there’s no better way? Is there some
CustomVerboseDebugStringConvertible protocol we can override to change only
the "MyFramework.CoinSide.heads" form?

If indeed there is no better way, it seems like a really good case for
having the synthesized .caseName property. Even if there is a
CustomVerboseDebugStringConvertible to override in the particular case
above, being able to customize an enum’s description but still use the enum
case name in that description seems like a compelling use case as well.

Cheers, P

On Jun 1, 2016, at 10:47 AM, Leonardo Pessoa <me@lmpessoa.com> wrote:

Paul, in all my tests for this thread printing the enum value only
produced the enum value's name ("Mars" in your example). The proposal
of having a .caseName (or should it better be .caseValue to cover
enums with associated values? any other suggestions?) will prevent
that changes to this behaviour crash apps in the future as this should
always produce the same result even if the string representation
changes.

L

On 1 June 2016 at 12:15, Paul Cantrell via swift-evolution > <swift-evolution@swift.org> wrote:

IIRC, string interpolation prepends the module name if the enum belongs to a
module: “MyLib.Mars” instead of just “Mars”. It’s also been a source of
compiler crashes, at least in the past.

Those two factors forced me into this ugliness:
https://github.com/bustoutsolutions/siesta/blob/master/Source/ResourceObserver.swift#L106-L115

A clean, documented, supported way of exposing the enum case name that the
runtime clearly already has available seems sensible — and should be
independent of the raw type.

Cheers, P

On Jun 1, 2016, at 5:10 AM, Charlie Monroe via swift-evolution > <swift-evolution@swift.org> wrote:

This is, however, kind of a hack IMHO that relies on the compiler behavior
that isn't well documented.

For example, this:

enum Planet {
     case Earth
     case Mars
}

"\(Planet.Mars)" // This is "Mars"

Works as well. You don't need to have the represented value to be String.

Note that this:

- works both when you have a plain enum, or enum Planet: Int, or whatever
raw value kind
- does not work (!) when declared as @objc - then the result is "Planet".

On Jun 1, 2016, at 9:52 AM, Patrick Smith via swift-evolution > <swift-evolution@swift.org> wrote:

I had no idea you could do this!!

On 1 Jun 2016, at 12:32 PM, Brent Royal-Gordon via swift-evolution > <swift-evolution@swift.org> wrote:

Who said anything about repeating the name?

Welcome to Apple Swift version 2.2 (swiftlang-703.0.18.8 clang-703.0.30).
Type :help for assistance.
1> enum Planet: String { case mercury, venus, earth, mars, jupiter, saturn,
uranus, neptune }
2> Planet.mercury.rawValue
$R0: String = "mercury"

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Brent, for needing "both Int and Double values" there is a proposal to add tuples instead of the current raw values or allowing assessor properties per case, you should check those out. Perhaps this could also be used to cryptoghaphically sign a raw value but I'm not sure.

As for working with enum values by name a few examples have already been posted in today but I've done a lot more research in the subject along the day and found there is a correlation between enums and nominal level values in statistics; we cannot test them for a particular order (this could also be interesting for statistic apps but it's another case) and no math with them is valid. So, e.g., the result of the following operation on the planets enum is nonsense:

  let planet = Planet(rawValue: Planet.Mars.rawValue - Planet.Mercury.rawValue)

The result will be different if the enum values are zero based than if not. Also any change in list order or the base index or add a new element to the middle of the list will break your intended code if you're storing the raw value in a database. And we know these changes happen. Actually, given this characteristic of nominal types (statistic), we should vow to removing init(rawValue:) completely from the language.

The real value you're working with in enums is the enum case name not any associated values. By working with the name you're safe should any associated value change, should their order change, you'll only break your app if the case is removed/renamed (with the raw value, you risk having the wrong treatment being given should another enum case takes the value of a removed one).

I agree there are lots of important and more difficult things to review in the language but I wouldn't be wasting my time here if I didn't think this was equally important.

L

···

-----Original Message-----
From: "Brent Royal-Gordon" <brent@architechies.com>
Sent: ‎01/‎06/‎2016 06:10 PM
To: "Leonardo Pessoa" <me@lmpessoa.com>
Cc: "Vladimir.S" <svabox@gmail.com>; "swift-evolution" <swift-evolution@swift.org>
Subject: Re: [swift-evolution] Working with enums by name

This should work but feels like an ugly hack to me. What if I needed
the enum like this?

> enum Size : Double {
> case Fit = 0.5
> case Fill = 3.0
> }

What if you needed both Int and Double rawValues? What if you needed rawValues that were cryptographically signed? We have to decide which use cases are common enough to support directly in the language, and I'm not convinced that "I need to look cases up by name, but I have no choice but to use rawValue for something else" is one of them—that is, that it's *so* common that we need to direct our scarce engineering resources towards designing and implementing a separate feature merely to accommodate it. There are a whole lot of things that are *way* higher on our to-do list than this, arguably including metaprogramming features which would let you write this yourself instead of sticking it in the core language.

--
Brent Royal-Gordon
Architechies

This will not print the name of case :

enum E: CustomStringConvertible {
     case one, two

     var description: String {return "haha"}
}

print(E.one)

So, for me, it seems like the good idea to have a standard(and built-in) way to convert string<->case i.e. to have

let e = E(caseName: "one")!
and
let s = e.caseName // always the same as defined in enum type

···

On 01.06.2016 18:47, Leonardo Pessoa via swift-evolution wrote:

Paul, in all my tests for this thread printing the enum value only
produced the enum value's name ("Mars" in your example). The proposal
of having a .caseName (or should it better be .caseValue to cover
enums with associated values? any other suggestions?) will prevent
that changes to this behaviour crash apps in the future as this should
always produce the same result even if the string representation
changes.

L

On 1 June 2016 at 12:15, Paul Cantrell via swift-evolution > <swift-evolution@swift.org> wrote:

IIRC, string interpolation prepends the module name if the enum belongs to a module: “MyLib.Mars” instead of just “Mars”. It’s also been a source of compiler crashes, at least in the past.

Those two factors forced me into this ugliness: https://github.com/bustoutsolutions/siesta/blob/master/Source/ResourceObserver.swift#L106-L115

A clean, documented, supported way of exposing the enum case name that the runtime clearly already has available seems sensible — and should be independent of the raw type.

Cheers, P

On Jun 1, 2016, at 5:10 AM, Charlie Monroe via swift-evolution <swift-evolution@swift.org> wrote:

This is, however, kind of a hack IMHO that relies on the compiler behavior that isn't well documented.

For example, this:

enum Planet {
      case Earth
      case Mars
}

"\(Planet.Mars)" // This is "Mars"

Works as well. You don't need to have the represented value to be String.

Note that this:

- works both when you have a plain enum, or enum Planet: Int, or whatever raw value kind
- does not work (!) when declared as @objc - then the result is "Planet".

On Jun 1, 2016, at 9:52 AM, Patrick Smith via swift-evolution <swift-evolution@swift.org> wrote:

I had no idea you could do this!!

On 1 Jun 2016, at 12:32 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Who said anything about repeating the name?

Welcome to Apple Swift version 2.2 (swiftlang-703.0.18.8 clang-703.0.30). Type :help for assistance.
1> enum Planet: String { case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune }
2> Planet.mercury.rawValue
$R0: String = "mercury"

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Can't agree with Brent's opinion. For me the proposed init(caseName:String) and .caseName property for enum case - seems like base feature that enum type must to have.

As for string as raw value for enum, please find this example:

enum E: String {
     case one = "One"
     case two = "Two"
}

print(E.one.rawValue) // One
print(E(rawValue: "one")) // nil
print(E(rawValue: "One")) // Optional(main.E.one)

How do you suggest to transform the case name("one") to enum case variable? `init(rawValue:)` will accept assigned rawValue, not case name. Even worse, conforming to CustomStringConvertable can prevent you from have "one" as result String(E.one).

···

On 02.06.2016 0:10, Brent Royal-Gordon wrote:

This should work but feels like an ugly hack to me. What if I needed
the enum like this?

> enum Size : Double {
> case Fit = 0.5
> case Fill = 3.0
> }

What if you needed both Int and Double rawValues? What if you needed rawValues that were cryptographically signed? We have to decide which use cases are common enough to support directly in the language, and I'm not convinced that "I need to look cases up by name, but I have no choice but to use rawValue for something else" is one of them—that is, that it's *so* common that we need to direct our scarce engineering resources towards designing and implementing a separate feature merely to accommodate it. There are a whole lot of things that are *way* higher on our to-do list than this, arguably including metaprogramming features which would let you write this yourself instead of sticking it in the core language.

Brent, for needing "both Int and Double values" there is a proposal to add tuples instead of the current raw values or allowing assessor properties per case, you should check those out. Perhaps this could also be used to cryptoghaphically sign a raw value but I'm not sure.

I know; I was the one who suggested accessors.

As for working with enum values by name a few examples have already been posted in today but I've done a lot more research in the subject along the day and found there is a correlation between enums and nominal level values in statistics; we cannot test them for a particular order (this could also be interesting for statistic apps but it's another case) and no math with them is valid. So, e.g., the result of the following operation on the planets enum is nonsense:

> let planet = Planet(rawValue: Planet.Mars.rawValue - Planet.Mercury.rawValue)

The result will be different if the enum values are zero based than if not. Also any change in list order or the base index or add a new element to the middle of the list will break your intended code if you're storing the raw value in a database. And we know these changes happen.

All of this is true. And all of this is an argument that *you're using raw values wrong*.

Raw values are a serialization mechanism. You might be serializing merely within your process, or you might be writing out to disk, through IPC, or across the network, but in all cases you are serializing. You should not be doing arithmetic with a serialized representation (unless that arithmetic is a part of the serialization process, like an error-correcting code you're applying to it). You should not be sorting serialized representations. You should be either communicating the raw value or recreating the instance with it—nothing else.

In other words, all of these are arguments for putting the order of the Planet in a separate property rather than in the `rawValue`. This would free up the `rawValue` to be a `String` containing the case name. This is not an argument for having both an Int `rawValue` and a String `caseName`; this is an argument for having both a String `rawValue` and an Int property.

  enum Planet: String {
    accessor var order: Int
    
    case mercury { order = 0 }
    case venus { order = 1 }
    ...etc...
  }

(Want it to be automatic? One could imagine having a compiler substitution for "the index of this case" which could be used as the default value for an accessor:

  enum Planet: String {
    accessor var order: Int = #caseIndex
    case mercury, venus, ...
  }

Or let you choose a base:

  enum Planet: String {
    accessor var order: Int = #caseOrder(from: 1)
    case mercury, venus, ...
  }

Or the `ValuesEnumerable` proposal would give you a convenient, though slightly slow, way to do two-way lookup by order:

  enum Planet: String, ValuesEnumerable {
    var order: Int {
      return Planet.allValues.index(of: self)!
    }
    init(order: Int) {
      self = Planet.allValues[order]
    }
    case mercury, venus, …
  }

In short, there are several plausible mechanisms to automate assignment of these numbers, should you want to do that.)

Actually, given this characteristic of nominal types (statistic), we should vow to removing init(rawValue:) completely from the language.

That doesn't follow. The fact that changing something would break code doesn't mean that thing should be removed; it means you should be cautious about changing that thing. If you rename a case, its name changes; does that mean we shouldn't have case name lookups either? Changing any identifier could break something; maybe we should abolish all identifiers from Swift?

The real value you're working with in enums is the enum case name not any associated values. By working with the name you're safe should any associated value change, should their order change, you'll only break your app if the case is removed/renamed (with the raw value, you risk having the wrong treatment being given should another enum case takes the value of a removed one).

RawRepresentable is a serialization mechanism, so *of course* changing the raw value can break serialization. This is true whether you're using `Int` raw values or `String` raw values; it's just that `Int`s are a little easier to change.

If you create an enum with raw values, the raw values are more or less part of that enum's contract. You shouldn't expect things to continue to work properly if you change them, any more than you should expect them to continue to work properly if you delete a case. In fact, the library resilience document specifically calls out changing raw values as a "binary-compatible source-breaking change": <https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums&gt;

I agree there are lots of important and more difficult things to review in the language but I wouldn't be wasting my time here if I didn't think this was equally important.

I don't see how it's particularly important to solve this bizarre corner case of needing both a non-String raw value *and* a way to look up cases by name, particularly since you *still* have not provided a decent example where the non-String rawValue ought to be a rawValue at all.

···

--
Brent Royal-Gordon
Architechies

I understand you don't like to rely on the name of your enum cases in you code and that's fine, you can still work with them as is, you won't have to change the way you work just because you don't like the proposal but that doesn't mean everybody has the same opinion you do. But perhaps then instead of enums we should go back to using simple constants because that's exactly what enum cases become if their names are not how you're expected to reference, store and retrieve their values (yes, I understand there are other benefits to using enums instead of constants but that's just how you're using them).

L

···

-----Original Message-----
From: "Brent Royal-Gordon" <brent@architechies.com>
Sent: ‎02/‎06/‎2016 01:40 AM
To: "Leonardo Pessoa" <me@lmpessoa.com>
Cc: "Vladimir.S" <svabox@gmail.com>; "swift-evolution" <swift-evolution@swift.org>
Subject: Re: [swift-evolution] Working with enums by name

Brent, for needing "both Int and Double values" there is a proposal to add tuples instead of the current raw values or allowing assessor properties per case, you should check those out. Perhaps this could also be used to cryptoghaphically sign a raw value but I'm not sure.

I know; I was the one who suggested accessors.

As for working with enum values by name a few examples have already been posted in today but I've done a lot more research in the subject along the day and found there is a correlation between enums and nominal level values in statistics; we cannot test them for a particular order (this could also be interesting for statistic apps but it's another case) and no math with them is valid. So, e.g., the result of the following operation on the planets enum is nonsense:

> let planet = Planet(rawValue: Planet.Mars.rawValue - Planet.Mercury.rawValue)

The result will be different if the enum values are zero based than if not. Also any change in list order or the base index or add a new element to the middle of the list will break your intended code if you're storing the raw value in a database. And we know these changes happen.

All of this is true. And all of this is an argument that *you're using raw values wrong*.

Raw values are a serialization mechanism. You might be serializing merely within your process, or you might be writing out to disk, through IPC, or across the network, but in all cases you are serializing. You should not be doing arithmetic with a serialized representation (unless that arithmetic is a part of the serialization process, like an error-correcting code you're applying to it). You should not be sorting serialized representations. You should be either communicating the raw value or recreating the instance with it—nothing else.

In other words, all of these are arguments for putting the order of the Planet in a separate property rather than in the `rawValue`. This would free up the `rawValue` to be a `String` containing the case name. This is not an argument for having both an Int `rawValue` and a String `caseName`; this is an argument for having both a String `rawValue` and an Int property.

  enum Planet: String {
    accessor var order: Int
    
    case mercury { order = 0 }
    case venus { order = 1 }
    ...etc...
  }

(Want it to be automatic? One could imagine having a compiler substitution for "the index of this case" which could be used as the default value for an accessor:

  enum Planet: String {
    accessor var order: Int = #caseIndex
    case mercury, venus, ...
  }

Or let you choose a base:

  enum Planet: String {
    accessor var order: Int = #caseOrder(from: 1)
    case mercury, venus, ...
  }

Or the `ValuesEnumerable` proposal would give you a convenient, though slightly slow, way to do two-way lookup by order:

  enum Planet: String, ValuesEnumerable {
    var order: Int {
      return Planet.allValues.index(of: self)!
    }
    init(order: Int) {
      self = Planet.allValues[order]
    }
    case mercury, venus, …
  }

In short, there are several plausible mechanisms to automate assignment of these numbers, should you want to do that.)

Actually, given this characteristic of nominal types (statistic), we should vow to removing init(rawValue:) completely from the language.

That doesn't follow. The fact that changing something would break code doesn't mean that thing should be removed; it means you should be cautious about changing that thing. If you rename a case, its name changes; does that mean we shouldn't have case name lookups either? Changing any identifier could break something; maybe we should abolish all identifiers from Swift?

The real value you're working with in enums is the enum case name not any associated values. By working with the name you're safe should any associated value change, should their order change, you'll only break your app if the case is removed/renamed (with the raw value, you risk having the wrong treatment being given should another enum case takes the value of a removed one).

RawRepresentable is a serialization mechanism, so *of course* changing the raw value can break serialization. This is true whether you're using `Int` raw values or `String` raw values; it's just that `Int`s are a little easier to change.

If you create an enum with raw values, the raw values are more or less part of that enum's contract. You shouldn't expect things to continue to work properly if you change them, any more than you should expect them to continue to work properly if you delete a case. In fact, the library resilience document specifically calls out changing raw values as a "binary-compatible source-breaking change": <https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst#enums&gt;

I agree there are lots of important and more difficult things to review in the language but I wouldn't be wasting my time here if I didn't think this was equally important.

I don't see how it's particularly important to solve this bizarre corner case of needing both a non-String raw value *and* a way to look up cases by name, particularly since you *still* have not provided a decent example where the non-String rawValue ought to be a rawValue at all.

--
Brent Royal-Gordon
Architechies

Great points Brent. I think the ValuesEnumerable method would be the most straight forward. Also, the number of cases are likely only going to be in range of 6–20, so iterating would be fine I think. People can create something like `Dictionary(Planet.allValues.enumerated().lazy.map{ ($1, $0) })` (I think that’s right) if they really need.

···

On 2 Jun 2016, at 2:40 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Or the `ValuesEnumerable` proposal would give you a convenient, though slightly slow, way to do two-way lookup by order:

  enum Planet: String, ValuesEnumerable {
    var order: Int {
      return Planet.allValues.index(of: self)!
    }
    init(order: Int) {
      self = Planet.allValues[order]
    }
    case mercury, venus, …
  }

There are several ways to solve this, which IMO is a basic functionality of enums, writing code that is currently possible and works. But that's the issue, you still have to write code to have a basic functionally. I don't remember not being able to do this out-of-the-box in any language I worked with.

L

···

-----Original Message-----
From: "Patrick Smith" <pgwsmith@gmail.com>
Sent: ‎02/‎06/‎2016 02:07 AM
To: "Brent Royal-Gordon" <brent@architechies.com>
Cc: "Leonardo Pessoa" <me@lmpessoa.com>; "swift-evolution" <swift-evolution@swift.org>
Subject: Re: [swift-evolution] Working with enums by name

Great points Brent. I think the ValuesEnumerable method would be the most straight forward. Also, the number of cases are likely only going to be in range of 6–20, so iterating would be fine I think. People can create something like `Dictionary(Planet.allValues.enumerated().lazy.map{ ($1, $0) })` (I think that’s right) if they really need.

On 2 Jun 2016, at 2:40 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Or the `ValuesEnumerable` proposal would give you a convenient, though slightly slow, way to do two-way lookup by order:

  enum Planet: String, ValuesEnumerable {
    var order: Int {
      return Planet.allValues.index(of: self)!
    }
    init(order: Int) {
      self = Planet.allValues[order]
    }
    case mercury, venus, …
  }

From what I understand, enums normally are represented internally by an offset — that is their truth. With RawRepresentable enums, you are saying “no, I want the truth to be something else”. But it seems that they are still represented internally by an offset, so you can’t reorder a RawRepresentable enum’s cases and maintain ABI compatibility either.

So what you are saying about the order of cases being an intrinsic part of an enum does make sense. I’m not sure if can still lead to confusing / fragile code though.

Patrick

···

On 2 Jun 2016, at 10:17 PM, Leonardo Pessoa <me@lmpessoa.com> wrote:

There are several ways to solve this, which IMO is a basic functionality of enums, writing code that is currently possible and works. But that's the issue, you still have to write code to have a basic functionally. I don't remember not being able to do this out-of-the-box in any language I worked with.

L
From: Patrick Smith <mailto:pgwsmith@gmail.com>
Sent: ‎02/‎06/‎2016 02:07 AM
To: Brent Royal-Gordon <mailto:brent@architechies.com>
Cc: Leonardo Pessoa <mailto:me@lmpessoa.com>; swift-evolution <mailto:swift-evolution@swift.org>
Subject: Re: [swift-evolution] Working with enums by name

Great points Brent. I think the ValuesEnumerable method would be the most straight forward. Also, the number of cases are likely only going to be in range of 6–20, so iterating would be fine I think. People can create something like `Dictionary(Planet.allValues.enumerated().lazy.map{ ($1, $0) })` (I think that’s right) if they really need.

> On 2 Jun 2016, at 2:40 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:
>
> Or the `ValuesEnumerable` proposal would give you a convenient, though slightly slow, way to do two-way lookup by order:
>
> enum Planet: String, ValuesEnumerable {
> var order: Int {
> return Planet.allValues.index(of: self)!
> }
> init(order: Int) {
> self = Planet.allValues[order]
> }
> case mercury, venus, …
> }

Patrick, I never said the order of the enum was intrinsic. If the offset of the case were the truth of the enum as you said enums would be ordinal types and you could do tests like "Planet.Mercury > Planet.Venus" without any extra code. But enums are nominal types not ordinal types so you can only distinguish the different values but they have no particular order or any other property. Two simple examples of nominal types are colours and cardinal points. Do they have any particular order? That's how enums are implemented in Swift (as a nominal type) and that's another reason why working with case names makes even more sense.

L

···

-----Original Message-----
From: "Patrick Smith" <pgwsmith@gmail.com>
Sent: ‎02/‎06/‎2016 11:18 PM
To: "Leonardo Pessoa" <me@lmpessoa.com>
Cc: "Brent Royal-Gordon" <brent@architechies.com>; "swift-evolution" <swift-evolution@swift.org>
Subject: Re: [swift-evolution] Working with enums by name

From what I understand, enums normally are represented internally by an offset — that is their truth. With RawRepresentable enums, you are saying “no, I want the truth to be something else”. But it seems that they are still represented internally by an offset, so you can’t reorder a RawRepresentable enum’s cases and maintain ABI compatibility either.

So what you are saying about the order of cases being an intrinsic part of an enum does make sense. I’m not sure if can still lead to confusing / fragile code though.

Patrick

On 2 Jun 2016, at 10:17 PM, Leonardo Pessoa <me@lmpessoa.com> wrote:

There are several ways to solve this, which IMO is a basic functionality of enums, writing code that is currently possible and works. But that's the issue, you still have to write code to have a basic functionally. I don't remember not being able to do this out-of-the-box in any language I worked with.

L

From: Patrick Smith
Sent: ‎02/‎06/‎2016 02:07 AM
To: Brent Royal-Gordon
Cc: Leonardo Pessoa; swift-evolution
Subject: Re: [swift-evolution] Working with enums by name

Great points Brent. I think the ValuesEnumerable method would be the most straight forward. Also, the number of cases are likely only going to be in range of 6–20, so iterating would be fine I think. People can create something like `Dictionary(Planet.allValues.enumerated().lazy.map{ ($1, $0) })` (I think that’s right) if they really need.

On 2 Jun 2016, at 2:40 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Or the `ValuesEnumerable` proposal would give you a convenient, though slightly slow, way to do two-way lookup by order:

enum Planet: String, ValuesEnumerable {
var order: Int {
return Planet.allValues.index(of: self)!
}
init(order: Int) {
self = Planet.allValues[order]
}
case mercury, venus, …
}