enum SolarSystemPlanet: String, CaseIterable {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
func toRawValue(_ value: SolarSystemPlanet) -> PlanetName {
value.rawValue
}
}
With the enum above, one way to get an array of planet names is to call
SolarSystemPlanet.allCases.map { $0.rawValue }
But Swift supports first-class functions, treating functions as "first class citizens", which allows us to call functions just like any other object or value.
So it would be nice to get an array of names by this
SolarSystemPlanet.allCases.map(.toRawValue)
However, it seems like the compiler needs more context. It couldn't infer a type in map
at compile-time, so I did
SolarSystemPlanet.allCases.map(SolarSystemPlanet.toRawValue)
The compiler stops complaining, but I'm not getting an array of String. The line above returns a value of type [(SolarSystemPlanet) -> String]
If I printed the above out, instead of getting
["mercury", "venus", "earth", "mars", "jupiter", "saturn", "uranus", "neptune"]
I got
[(Function), (Function), (Function), (Function), (Function), (Function), (Function), (Function)]
If I forced the return type to be [String]
like this
var planets: [String] = SolarSystemPlanet.allCases.map(SolarSystemPlanet.toRawValue)
Xcode would complain that [(SolarSystemPlanet) -> String]
can't be converted to [String]
Is it possible after all to achieve what I'm trying to do? Am I missing something or doing something wrong?
And if it's not possible, I would also really appreciate some explanations on why.
Thanks for spending time reading my question!
Edit Thanks for @sweeper's answer.
For those who are interested, I went slightly further to make sure every String enum has toRawValue
extension RawRepresentable where RawValue == String {
static func toRawValue(_ value: Self) -> PlanetName {
value.rawValue
}
}
Note: This is Swift 5.1.3
Note that toRawValue
doesn't need to be an instance method. It can be static:
static func toRawValue(_ value: SolarSystemPlanet) -> PlanetName {
value.rawValue
}
Now you can use SolarSystemPlanet.toRawValue
as the argument to map
.
Alternatively, in this case, you can also use the keypath of \.rawValue
as the argument to map
:
SolarSystemPlanet.allCases.map(\.rawValue)
This is a new feature of Swift 5.2.
EDIT: Explanation of why instance methods don't work
In Swift, when accessed from a static context, an instance method on a type T
that has the signature (U) -> R
becomes a static method with the signature (T) -> ((U) -> R)
. Instance methods need an instance of the enclosing type to call, right? So when you pass it a T
, it gives you the original instance function (U) -> R
back.
Therefore, the type of the non-static SolarSystemPlanet.toRawValue
is
(SolarSystemPlanet) -> ((SolarSystemPlanet) -> String)
This explains why after map
is applied, the array becomes a [(SolarSystemPlanet) -> String]
.