I want to associate two raw values to an enum instance (imagine an enum representing error types, I want Error.Teapot to have an Int type property code with value 418, and a String property set to I'm a teapot.)
Note the difference between raw values and associated values here—I want all Teapot instances to have a code of 418, I don't want a unique associated value for each Teapot instance.
Is there a better way than adding computed properties to the enum that switched on self to look up the appropriate value?
You have a couple options. But neither of them involve raw values. Raw values are just not the right tool for the task.
Option 1 (so-so): Associated Values
I personally highly recommend against there being more than one associated value per enum case. Associated values should be dead obvious (since they don't have arguments/names), and having more than one heavily muddies the water.
That said, it's something the language lets you do. This allows you to have each case defined differently as well, if that was something you needed. Example:
enum ErrorType {
case teapot(String, Int)
case skillet(UInt, [CGFloat])
}
Option 2 (better): Tuples! And computed properties!
Tuples are a great feature of Swift because they give you the power of creating ad-hoc types. That means you can define it in-line. Sweet!
If each of your error types are going to have a code and a description, then you could have a computed info property (hopefully with a better name?). See below:
enum ErrorType {
case teapot
case skillet
var info: (code: Int, description: String) {
switch self {
case .teapot:
return (418, "Hear me shout!")
case .skillet:
return (326, "I'm big and heavy.")
}
}
}
Calling this would be much easier because you could use tasty, tasty dot syntax:
let errorCode = myErrorType.info.code
No, an enum cannot have multiple raw values - it has to be a single value, implementing the Equatable protocol, and be literal-convertible as described in the documentation.
I think the best approach in your case is to use the error code as raw value, and a property backed by a prepopulated static dictionary with the error code as key and the text as value.
I created a way of simulating this (No different than what Marcos Crispino suggested on his answer). Far from a perfect solution but allows us to avoid those nasty switch cases for every different property we want to get.
The trick is to use a struct as the "properties/data" holder and using it as a RawValue in the enum itself.
It has a bit of duplication but it's serving me well so far. Every time you want to add a new enum case, the compiler will remind you to fill in the extra case in the rawValue getter, which should remind you to update the init? which would remind you to create the new static property on the struct.
Gist
Code to the Gist:
enum VehicleType : RawRepresentable {
struct Vehicle : Equatable {
let name: String
let wheels: Int
static func ==(l: Vehicle, r: Vehicle) -> Bool {
return l.name == r.name && l.wheels == r.wheels
}
static var bike: Vehicle {
return Vehicle(name: "Bicycle", wheels: 2)
}
static var car: Vehicle {
return Vehicle(name: "Automobile", wheels: 4)
}
static var bus: Vehicle {
return Vehicle(name: "Autobus", wheels: 8)
}
}
typealias RawValue = Vehicle
case car
case bus
case bike
var rawValue: RawValue {
switch self {
case .car:
return Vehicle.car
case .bike:
return Vehicle.bike
case .bus:
return Vehicle.bus
}
}
init?(rawValue: RawValue) {
switch rawValue {
case Vehicle.bike:
self = .bike
case Vehicle.car:
self = .car
case Vehicle.bus:
self = .bus
default: return nil
}
}
}
VehicleType.bike.rawValue.name
VehicleType.bike.rawValue.wheels
VehicleType.car.rawValue.wheels
VehicleType(rawValue: .bike)?.rawValue.name => "Bicycle"
VehicleType(rawValue: .bike)?.rawValue.wheels => 2
VehicleType(rawValue: .car)?.rawValue.name => "Automobile"
VehicleType(rawValue: .car)?.rawValue.wheels => 4
VehicleType(rawValue: .bus)?.rawValue.name => "Autobus"
VehicleType(rawValue: .bus)?.rawValue.wheels => 8
No, you cannot have multiple raw values associated with an enum.
In your case, you could have the raw value to be equal to the code, and have an associated value with the description. But I think the computed properties approach is the best option here.
One workaround if you wanted to have many static properties for a YourError could be to import a property list; you could set the root object to a dictionary, with your enum raw value as the key for each object, allowing you to easily retrieve static structured data for the object.
This has an example of importing and using a plist: http://www.spritekitlessons.com/parsing-a-property-list-using-swift/
That might be overkill for simply an error description, for which you could just use a hardcoded static function with a switch statement for your enum values, that returns the error string you need. Simply place the static function in the same .swift file as your enum.
For instance,
static func codeForError(error : YourErrorType) -> Int {
switch(error) {
case .Teapot:
return "I'm a Teapot"
case .Teacup:
return "I'm a Teacup"
...
default:
return "Unknown Teaware Error"
}
}
This has the benefit (compared to the .plist solution) of better accomodating localization. However, a .plist could just contain a key used for retrieving the proper localization, instead of a error string, for this purpose.
For beginning, assuming you want to store a code and a message, you can use a struct for RawValue
struct ErrorInfo {
let code: Int
let message: String
}
Next step is to define the enum as being RawRepresentable, and use ErrorInfo as the raw value:
enum MyError: RawRepresentable {
typealias RawValue = ErrorInfo
case teapot
What remains is to map between instances of MyError and ErrorInfo:
static private let mappings: [(ErrorInfo, MyError)] = [
(ErrorInfo(code: 418, message: "I'm a teapot"), .teapot)
]
With the above, let's build the full definition of the enum:
enum MyError: RawRepresentable {
static private let mappings: [(ErrorInfo, MyError)] = [
(ErrorInfo(code: 418, message: "I'm a teapot"), .teapot)
]
case teapot
init?(rawValue: ErrorInfo) {
guard let match = MyError.mappings.first(where: { $0.0.code == rawValue.code && $0.0.message == rawValue.message}) else {
return nil
}
self = match.1
}
var rawValue: ErrorInfo {
return MyError.mappings.first(where: { $0.1 == self })!.0
}
}
Some notes:
you could use only the error code for matching, however this might result in inconsistent raw values if the messages differ
the amount of boilerplate code required to have raw values of some custom type might not outcome the benefits of using associated values.
Possible work around may to associate custom functions with enum
enum ToolbarType : String{
case Case = "Case", View="View", Information="Information"
static let allValues = [Case, View, Information]
func ordinal() -> Int{
return ToolbarType.allValues.index(of: self)!
}
}
Can be used as
for item in ToolbarType.allValues {
print("\(item.rawValue): \(item.ordinal())")
}
Output
Case: 0
View: 1
Information: 2
Possibly you can have additional functions to associate enum type to different values
This doesn't particularly answer your question, which was asking to find a better way than switching through self to look up the appropriate value but this answer may still be useful for someone looking in the future that needs a simple way to get a string from an enum which is defined as an integer type.
enum Error: UInt {
case Teapot = 418
case Kettle = 419
static func errorMessage(code: UInt) -> String {
guard let error = Error(rawValue: code) else {
return "Unknown Error Code"
}
switch error {
case .Teapot:
return "I'm a teapot!"
case .Kettle:
return "I'm a kettle!"
}
}
}
This way, we can get the errorMessage two ways:
With an integer (eg. that was returned as an error code from a server)
With an enum value (the rawValue we define for the enum)
Option 1:
let option1 = Error.errorMessage(code: 418)
print(option1) //prints "I'm a teapot!"
Option 2:
let option2 = Error.errorMessage(code: Error.Teapot.rawValue)
print(option2) //prints "I'm a teapot!"
In modern versions of Swift it's possible to get the string value of an enum case label, even without that enum being declared with a : String rawValue.
How to get the name of enumeration value in Swift?
So there is no longer a need to define and maintain a convenience function that switches on each case to return a string literal. In addition, this works automatically for any enum, even if no raw-value type is specified.
This, at least, allows you to have "multiple raw values" by having both a real : Int rawValue as well as the string used as the case label.
I think it just tricky, and I have create my own idea like below:
enum Gender:NSNumber
{
case male = 1
case female = 0
init?(strValue: String?) {
switch strValue {
case Message.male.value:
self = .male
case Message.female.value:
self = .female
default: return nil
}
}
var strValue: String {
switch self {
case .male:
return Message.male.value
case .female:
return Message.female.value
}
}
}
First of all, enums should only have one raw value. However if you want to have something that can use multiple raw values... there is a way to 'hack' this, but you have to make it codable and hashable yourself, implement custom init's etc.
enum MyCustomEnum: Codable, Hashable {
// duplicate every case with associated value of Codable.Type
case myFirstCase, _myFirstCase(Codable.Type)
case mySecondCase, _mySecondCase(Codable.Type)
case myThirdCase, _myThirdCase(Codable.Type)
case unknown(Any), _unknown(Codable.Type, Any) // handles unknown values
// define an allCases value to determine the only values your app 'sees'.
static var allCases: [Self] {
return [
.myFirstCase,
.mySecondCase,
.myThirdCase
// unknown(String) // you can add unknown as well, but this is too mask any unknown values.
]
}
static func == (lhs: MyCustomEnum, rhs: MyCustomEnum) -> Bool {
return lhs.stringValue == rhs.stringValue // can be either one of your custom raw values.
}
// add this per raw value. In this case one for Int and one for String
init(rawValue: Int) {
guard let value = Self.allCases.first(where:{ $0.intValue == rawValue }) else {
self = ._unknown(Int.self, rawValue)
return
}
switch value {
case .myFirstCase: self = ._myFirstCase(Int.self)
case .mySecondCase: self = ._mySecondCase(Int.self)
case .myThirdCase: self = ._myThirdCase(Int.self)
default: self = ._unknown(Int.self, rawValue)
}
}
init(rawValue: String) {
guard let value = Self.allCases.first(where:{ $0.stringValue == rawValue }) else {
self = ._unknown(String.self, rawValue)
return
}
switch value {
case .myFirstCase: self = ._myFirstCase(String.self)
case .mySecondCase: self = ._mySecondCase(String.self)
case .myThirdCase: self = ._myThirdCase(String.self)
default: self = ._unknown(Int.self, rawValue)
}
}
// add this per raw value. In this case one for Int and one for String
var intValue: Int {
switch self {
case .myFirstCase, ._myFirstCase(_): return 1
case .mySecondCase, ._mySecondCase(_): return 2
case .myThirdCase, ._myThirdCase(_): return 3
case .unknown(let value), ._unknown(_, let value): return value as? Int ?? -1 // you can also choose to let intValue return optional Int.
}
}
var stringValue: String {
switch self {
case .myFirstCase, ._myFirstCase(_): return "my first case"
case .mySecondCase, ._mySecondCase(_): return "my second case"
case .myThirdCase, ._myThirdCase(_): return "my third case"
case .unknown(let value), ._unknown(_, let value): return value as? String ?? "not a String" // you can also choose to let stringValue return optional String.
}
}
// determine the codable type using Mirror
private func getCodableType() -> Codable.Type? {
let mirrorOfModuleType = Mirror.init(reflecting: self)
guard let childOfModuleType = mirrorOfModuleType.children.first else { // no children, means no associated values.
return nil
}
let value = childOfModuleType.value // can be either Codable.Type, String or (Codable.Type & String)
if let rawValue = value as? Codable.Type {
return rawValue
} else {
guard let rawValue = value as? (Codable.Type, String) else {
// unknown(String), we don't know the rawValue as given, but try in this part of the code to guess what type fits best.
if self.stringValue != "\(self.intValue)" { // e.g. "1" might match 1 but "1.0" and 1 don't match
return String.self
} else {
return Int.self // return either a default value, or nil. It's your choice.
}
}
return rawValue.0
}
}
// confine to hashable using getCodableType
func hash(into hasher: inout Hasher) {
if self.getCodableType() is String.Type {
hasher.combine(self.stringValue)
} else { // if you don't call hasher.combine at all, you can expect strange issues. If you do not know the type, choose one that is most common.
hasher.combine(self.intValue)
}
}
// confine to Decodable
init(from decoder: Decoder) throws {
if let rawValue = try? Int.init(from: decoder) {
self.init(rawValue: rawValue)
} else if let rawValue = try? String.init(from: decoder) {
self.init(rawValue: rawValue)
} else {
throw DecodingError.valueNotFound(Self.self, DecodingError.Context(codingPath: [], debugDescription: "no matching value was found"))
}
}
// confine to Encodable using getCodableType
func encode(to encoder: Encoder) throws {
let rawValue = self.getCodableType()
if rawValue is String.Type {
try self.stringValue.encode(to: encoder)
} else if rawValue is Int.Type {
try self.intValue.encode(to: encoder)
} else {
// getCodableType returns nil if it does not know what value it is. (e.g. myFirstCase without associated value) If you want to support this as well, you can encode using one of your rawValues to the encoder.
throw EncodingError.invalidValue(Self.self, EncodingError.Context.init(codingPath: [], debugDescription: "this enum does not have a correct value", underlyingError: nil))
}
}
}
this code is scalable to any number of raw value as long as they are Codable
Related
I'm trying to use Swift's new Result type in such a way that the type of the .success associated value is a generic. The rather contrived sample code below works, but is there a way to simplify the type casting so that the compiler can infer the correct type for T?
enum FetchError : Error {
case unknownKey
}
enum FetchKey {
case getWidth
case getName
}
func fetchValue<T>(_ key:FetchKey) -> Result<T, FetchError> {
switch key {
case .getName:
// Ideally I would like to just use: return .success("Johnny Appleseed")
return Result<String, FetchError>.success("Johnny Appleseed") as! Result<T, FetchError>
case .getWidth:
return Result<Double, FetchError>.success(25.0) as! Result<T, FetchError>
#unknown default:
return .failure(.unknownKey)
}
}
// This explicit type declaration is also required.
let r:Result<String, FetchError> = fetchValue(.getName)
print(r)
To your question about the type-casting, you can definitely simplify it:
case .getName:
return .success("Johnny Appleseed" as! T)
This is ok if asking for the wrong type should be considered a programming error (and so should crash), and the results will never come from external sources. If the data could ever come from an external source, then you should never crash in response to it being wrong.
In that case, we should model that kind of error:
enum FetchError : Error {
case unknownKey
case invalidType
}
Then you can have a syntax very close to what you like by adding a function to do the (possibly failing) type conversion:
func fetchValue<Value>(_ key:FetchKey) -> Result<Value, FetchError> {
func checkType(_ value: Any) -> Result<Value, FetchError> {
guard let value = value as? Value else { return .failure(.invalidType) }
return .success(value)
}
switch key {
case .getName: return checkType("Johnny Appleseed")
case .getWidth: return checkType(25.0)
#unknown default: return .failure(.unknownKey)
}
}
That said, I'd do it this way to avoid the ugliness of required type annotations:
func fetch<Value>(_: Value.Type, forKey key: FetchKey) -> Result<Value, FetchError> { ... }
let r = fetch(String.self, forKey: .getName)
This follows the pattern of Codable.
Here's the whole solution together in one place in a few different ways:
Returning Result
enum FetchError : Error {
case unknownKey
case invalidType
}
enum FetchKey {
case width
case name
}
func fetch<Value>(_: Value.Type, forKey key: FetchKey) -> Result<Value, FetchError> {
func checkType(_ value: Any) -> Result<Value, FetchError> {
guard let value = value as? Value else { return .failure(.invalidType) }
return .success(value)
}
switch key {
case .name: return checkType("Johnny Appleseed")
case .width: return checkType(25.0)
#unknown default: return .failure(.unknownKey)
}
}
With throws
I think this gets a little nicer if you throw rather than wrapping things into Result. It means you can more easily lift checkType into one place and gets very close to the syntax you said you wanted.
func fetch<Value>(_: Value.Type, forKey key: FetchKey) throws -> Value {
func checkType(value: () throws -> Any) throws -> Value {
guard let value = try value() as? Value else { throw FetchError.invalidType }
return value
}
return try checkType {
switch key {
case .name: return "Johnny Appleseed"
case .width: return 25.0
#unknown default: throw FetchError.unknownKey
}
}
}
With Optionals
This gets a little simpler with Optional if you don't really care about the errors.
func fetch<Value>(_: Value.Type, forKey key: FetchKey) -> Value? {
func _fetch() -> Any? {
switch key {
case .name: return "Johnny Appleseed"
case .width: return 25.0
#unknown default: return nil
}
}
return _fetch() as? Value
}
The function you are trying to create has a dynamically typed generic. This isn't possible as the Swift compiler needs to know the types for each method/variable at compile time.
Suppose you have
func whatToFetch() -> FetchKey {
// Randomly returns one of the FetchKey cases
}
let r = fetchValue(whatToFetch())
There is no way for the compiler to know the type of r before the call is made. Your example code works because your force-casts are perfectly lined up. But in a correct implementation you should not have to specify what type T is inside your generic function
In your case, it seems generics are not really needed and you could do away with protocols:
enum FetchKey {
case someStringValueKey
case someDoubleValueKey
case someModelValueKey
}
protocol FetchResult {}
extension String: FetchResult {}
extension Double: FetchResult {}
extension SomeModel: FetchResult {}
func fetch(_ key: FetchKey) -> Result<FetchResult,Error> {
switch key {
case .someStringValueKey:
return .success("")
case .someDoubleValueKey:
return .success(1.0)
case .someModelValueKey:
return .success(SomeModel())
}
}
let wtf = whatToFetch()
let r = fetch(wtf)
switch r {
case .success(let value):
switch wtf {
case .someStringValueKey:
guard let stringValue = value as? String else { return }
case .someDoubleValueKey:
guard let doubleValue = value as? Double else { return }
case .someModelValueKey:
guard let someModelValue = value as? SomeModel else { return }
}
case .failure(let error):
print("Better do more than just print on production code")
}
Trying to use an unqualified generic fetchValue<T> to do what you want probably isn’t going to work. The reason is that in a generic function, the T is specified by the caller, not the function. You’re essentially saying, “Ask fetchValue for any T you want, and it will give you a result.”
There’s missing link in the way you’ve set up your types. The red flag that’s your primary clue to this is the use of as!. That’s a sign that you are making assumptions about type relationships that you’re not telling the compiler about.
What does that mean? Well, note that with your code, let r: Result<URLConnection, FetchError> = fetchValue(.getName) also compiles — but then crashes at runtime!
One solution is to have an umbrella type that gathers all the possible result types, as Emil’s solution does. In this approach, you erase the type of the result, and ask callers to dynamically extract it. Callers have to deal with the possibility that the result might be of any type, not necessarily the one they expected. This is a common pattern when dealing with dynamically structured data such as JSON documents.
A second approach is to do what Rob suggests: let the caller specify the type, but translate an incorrect type into an error.
A third approach to solving this is to find a way to associate keys with result types in the type system, i.e. to tell the compiler getName → String, getWidth → Double. Unfortunately, AFAIK, there’s no way to do that with individual enum cases, so you’ll need to encode the keys as something other than an enum.
Here’s one way to do it:
enum FetchError : Error {
case unknownKey
}
protocol FetchKey {
associatedtype ValueType
func get() -> ValueType
}
struct GetNameKey: FetchKey {
func get() -> String { // This line establishes the getName → String mapping
return "Johnny Appleseed"
}
}
struct GetWidthKey: FetchKey {
func get() -> Double { // This line establishes the getWidth → Double mapping
return 25.0
}
}
// This function signature means: “If you give fetchValue a key, it
// will give you a result _with the appropriate type_ for that key”
//
func fetchValue<K: FetchKey>(_ key: K) -> Result<K.ValueType, FetchError> {
// The return type here is Result<K.ValueType, FetchError>, but
// now Swift has enough into to infer it!
return Result.success(key.get())
}
// This now works without type inference:
let r0 = fetchValue(GetNameKey())
print(r0)
// And this now (correctly) fails to compile:
// let r1: Result<Double, FetchError> = fetchValue(GetNameKey())
Which approach should you use?
Does each key always return a single, consistent type that you know beforehand (e.g. name is always a string)?
Yes: Use my approach above
No: If a fetch succeeds, but the type that came back isn’t the type the caller asked for, how do you want it reported?
As a Result.failure, and the caller can’t see the value: Use Rob’s approach
As a Result.success, and the caller has to figure out what type the value is: Use Emil’s approach
I have problem with declaring an Array and initialize it with many different generic items and in the same time use those items individually with generic support. Lets look at this:
protocol Item {
associatedtype ValueType: Any
var value: ValueType? { get set }
}
class ItemClass<T: Any>: Item {
typealias ValueType = T
var value: ValueType?
}
let intItem = ItemClass<Int>()
let stringItem = ItemClass<String>()
let array: [ItemClass<Any>] = [intItem, stringItem]
// Iterate over items and use `value` property as Any?
for item in array {
let val: Any? = item.value
// do something with val
}
// Individual usage with generic types support
let intValue: Int? = intItem.value
let stringValue: String? = stringItem.value
Why there is an error in array declaration like this:
Cannot convert value of type 'ItemClass<Int>' to expected element type 'ItemClass<Any>'
This entire approach is incorrect, and you can see that by the consuming code:
// Iterate over items and use `value` property as Any?
for item in array {
let val: Any? = item.value
// do something with val
}
In that "do something with val," what can you possibly do? There are no methods on Any. If you're going to do something like as? T, then you've broken the whole point of the types, because you don't mean "any". You mean "some list of types I know about." If you want "some list of types I know about," that's an enum with associated data, not a protocol with an associated type.
enum Item {
case string(String)
case int(Int)
var stringValue: String? {
guard case .string(let value) = self else { return nil }
return value
}
var intValue: Int? {
guard case .int(let value) = self else { return nil }
return value
}
}
let intItem = Item.int(4)
let stringItem = Item.string("value")
let array: [Item] = [intItem, stringItem]
// Iterate over items and use `value` property as Any?
for item in array {
switch item {
case let .string(value): break // Do something with string
case let .int(value): break // Do something with int
}
}
// Individual usage with generic types support
let intValue: Int? = intItem.intValue
let stringValue: String? = stringItem.stringValue
If, on the other hand, you really mean "any type," then you're not going to be able to put them in a collection without hiding the values in a box that gets rid of any information about that type (i.e. "a type eraser"). Which you need comes down to your actual use case. There isn't a single answer; it's going to be driven by how you want to consume this data.
But if you need as? very much at all, you've done something wrong.
I have some Outputs in my code so I regrouped all that with an Enum String.
The problem is that I have some Outputs containing variable.
Is It possible to create an Enum who takes variable ?
Exemple with this string
print("The name of the Team is \(team.name)")
I wanted to do something like that:
enum Exemple: String {
case TEAM_NAME(name: String) = "The name of the Team is \(name)"}
print(Exemple.TEAM.NAME("Team 1").rawvalue)
Thank you
It's possible to have an enum with associated values for cases. But in order to get the output you're looking for you will need a function.
enum Example {
case teamName(name: String)
case teamId(id: Int)
func printName() {
switch self {
case .teamName(name: let name):
print(name)
default:
break
}
}
}
let team = Example.teamName(name: "team1")
team.printName() // prints team1
You can define an instance method or computed property for enum that will return a string value in depend of enumeration case and associated value. See example for playground.
enum Example {
case firstItem
case secondItem(withText: String)
var stringValue: String {
switch self {
case .firstItem: return "Simple string"
case .secondItem(withText: let text): return "String with additional text: \(text)"
}
}
}
let myEnumItem: Example = .secondItem(withText: "Test")
let text = myEnumItem.stringValue
It is not possible for Enum to have both raw value and associated value. I think you can get by with the associated value. Without the raw value the enum still can provide you enough information to compose the message
I have a variety of enums such as below.
enum PaperOrientation : Int { case portrait, landscape }
enum MetricType : Int { case inches, metric }
I made the enums of type Int, so that the values of instances could be saved as numbers with CoreData.
When retrieving the values from CoreData to use in the program, I end up with very similar conversion routines, like those shown below.
Typically, I want some default value - such as for the case where it is a new enum for the latest version of the program, and a value for that variable may not actually have been saved in CoreData. For example, the MetricType was added for the second rev of the program. Retrieving a paper created in rev 1 will not have a metric value saved. For the nil value, I want to use a default value the paper was originally assumed to have.
class ConversionRoutine {
class func orientationFor(_ num: NSNumber?) -> PaperOrientation {
if let iVal = num?.intValue {
if let val = PaperOrientation(rawValue: iVal) {
return val
}
}
return PaperOrientation(rawValue: 0)!
}
class func metricTypeFor(_ num: NSNumber?) -> MetricType {
if let iVal = num?.intValue {
if let val = MetricType(rawValue: iVal) {
return val
}
}
return MetricType(rawValue: 0)!
}
}
Is there a way to reduce the redundancy?
I present a way below that works pretty well. But welcome more refinements or improvements.
The Swift 4 example below uses a Defaultable protocol based on RawRepresentable. The first step is creating a defaultValue that can be used when the initializer fails. Note that the Defaultable protocol is not limited to Int enums. A String enum could also use it.
protocol Defaultable : RawRepresentable {
static var defaultValue : Self { get }
}
protocol IntDefaultable : Defaultable where RawValue == Int {
}
extension IntDefaultable {
static func value(for intValue : Int) -> Self {
return Self.init(rawValue: intValue) ?? Self.defaultValue
}
static func value(for num : NSNumber?) -> Self {
if let iVal = num?.intValue {
return self.value(for: iVal)
}
return Self.defaultValue
}
}
After the Defaultable protocol is defined, I can create an IntDefaultable protocol that will be used for Int enums.
In an extension to IntDefaultable, I can create the generic code to handle the conversion. First, I create a function that takes an Int. Then I create a function that takes an NSNumber optional.
Next, look at how one of the enums is built:
enum MetricType : Int, Codable, IntDefaultable { case inches, metric
static var defaultValue: MetricType = .inches
}
I also decided to declare the enum Codable, which may be useful. When I add the IntDefaultable protocol, it becomes fairly easy to add the defaultValue line of code with code completion - go to the new line and type “def”-tab, then “ = .”, and then choose one of the values from the popup. Note that often I want to pick the first enum value, but the default value could be any one.
The last thing is calling the conversion routine for getting a value from CoreData
let units = MetricType.value(for: self.metricType) // where self.metricType is the NSManagedObject member.
You can add an initializer in enum.
enum PaperOrientation : Int {
case portrait, landscape
init(number: NSNumber) {
self = PaperOrientation(rawValue: number.intValue) ?? .portrait
}
}
Prior to Swift 2, I would often use enums with associated values and add functions to extract specific values, like so:
public enum Maybe <T> {
case Unknown
case Known(T)
public var value: T? {
switch self {
case .Unknown: return nil
case .Known(let value): return value
}
}
}
This would allow me to do something like this:
let maybe = .Known("Value")
let val = maybe.value ?? "Another Value"
I would like to get rid of these convenience functions and rely on Swift 2's new syntax. This is possible doing something like:
let val: String
if case .Known(let value) = maybe {
val = value
} else {
val = "Another Value"
}
But I can't figure out how to condense this back into a single line using the ?? operator or even ternary operator.
Is this even possible or am I stuck with defining "extraction" optionals on the enum?
Update (clarification)
The Maybe enum is just an example, but the solution would need to work on Enums that have multiple associated values... like an Either:
public enum Either<L, R> {
case Left(Box<L>)
case Right(Box<R>)
public func left() -> L?{
switch self {
case let Left(value):
return value.value
default:
return nil
}
}
public func right() -> R?{
switch self {
case let Right(value):
return value.value
default:
return nil
}
}
}
The syntax I'm looking for would be something like:
let val = (case .Known(let value) = maybe) ?? "AnotherValue"
What I want to do is easily extract an associated value for a specific case, else provide a default.
For Either it might be something like:
let val = (case .Left(let value) = either) ?? "AnotherValue"
Make sense?
The syntax you want isn't possible in Swift today (and feels unlikely for Swift tomorrow, but I often am surprised). The best available solutions are extraction functions like left() -> L? and right() -> R?. case is not a generic value-returning function that you can extend. See Rob Rix's Either for some of the best current thinking on this problem.
A key choice in Swift is that there are many statements that are not expressions. switch is one of them. Until Swift makes things like switch and if be expressions, it will be very hard to build this kind of syntax IMO.
Just define ?? for it:
func ??<T>(lhs: Maybe<T>, #autoclosure defaultValue: () throws -> T) rethrows -> T {
switch lhs {
case .Unknown: return try defaultValue()
case .Known(let value): return value
}
}
let maybe = Maybe.Known("Value")
let val = maybe ?? "Another Value"
Doing it this way gives us some nice features in Swift2. For instance, we can lazily evaluate the rhs, and we can handle throwing in the rhs:
func computeIt() -> String {
print("LAZY!")
return "Computed"
}
maybe ?? computeIt() // Does not print "LAZY!"
Maybe.Unknown ?? computeIt() // Does print "LAZY!"
enum Error: ErrorType {
case Failure
}
func fail() throws -> String {
throw Error.Failure
}
try maybe ?? fail() // Doesn't throw
do {
try Maybe.Unknown ?? fail() // throws
} catch {
print("THROW")
}