Extract associated value from enum regardless of the case in Swift - swift

I want to get the associated value of swift enum object, is there a way to do it shorter/better than in switch statement below?
enum Test {
case a(Int), b(Int), c(Int)
}
func printValue(_ t: Test) {
switch t {
case .a(let v), .b(let v), .c(let v): print("value \(v)")
}
}

Your code for extracting the associated value from multiple enums is the most economical and easy-to-read, there's no need to improve it.
However, the fact that you are looking to extract an associated value regardless of enum's case suggests that you are not using associated values correctly: rather than associating a value with each individual case, you should create a composite type that holds the Int and an enum without an associated value, i.e.
enum Test {
case a, b, c
}
class MyClass {
var num : Int
var tst : Test
}
Now that the associated value is "outside" each enum element, it can be accessed independently of the case, and you can also give it a meaningful name, which adds to readability of your program.

You might want to use mirror type - it's not the better way, but it can be helpful in some cases:
enum Test {
case a(Int), b(Int), c(Int)
}
func printValue(_ t: Test) {
let mirror = Mirror(reflecting: t)
print(mirror.children.first?.value ?? "")
}
printValue(.a(15))
Also using if/case like this, it's a shorter way if you need to extract value only from one case, sometimes it's helpful:
if case .a(let val) = t {
print("value \(val)")
}
Or may be raw value will fit better for your case:
enum Test: Int {
case a = 1
case b = 2
case c = 5
}
func printValue(_ t: Test) {
print("value \(t.rawValue)")
}
printValue(.a)

Related

Swift equivalent of Java Enum.valueOf

I found two answered questions about how to get an enum constant's name as a String. I want to do the vice-versa, i.e. getting the enum constant with a given name in a String:
enum Unit: Int {
case SECOND
case MINUTE
case HOUR
static func valueOf(unit: String) -> Unit {
// ?
}
}
I want to keep the rawValue to be an Int.
Update: To make the intention clear, I want to persist an object that has a Unit property. I also want to display the unit chosen by the user in the UI, as a localized string. Therefore I need to assign a constant and unique value, the integer, and a String to each enum value.
There is no easy built-in conversion. You can make the enum iterable and iterate over the strings util you find the correct one:
enum Unit: Int, CaseIterable {
case SECOND
case MINUTE
case HOUR
static func valueOf(unit: String) -> Unit? {
for x in Unit.allCases {
if String(describing: x)==unit {
return x
}
}
return nil
}
}
You'd need to deal the the possibility that an input string might not mach any valid case-string. I used an optional, but you could as well throw an exception.
Several remarks:
This code is case sensitive. So "HOUR" will not lead to the same result as "hour". If needed, add a case normalisation.
This code is not optimal, since it will perform a lot of string conversions every iteration on every call. A better alternative would be to initialise a dictionary once, and use it subsequently
The latter could look like:
private static var ready = false
private static var strings = [String : Unit] ()
static func valueOf2(unit: String) -> Unit? {
if !ready {
for x in Unit.allCases {
strings [String(describing: x)]=x
}
ready = true
}
return strings[unit]
}
Swift has a CaseIterable protocol that you can use to find the case from its raw value.
enum Unit: Int, CaseIterable {
case second = 100
case minute = 200
case hour = 300
static func caseFrom(rawValue: Int) -> Unit? {
return allCases.first { $0.rawValue == rawValue }
}
}
if let unitCase = Unit.caseFrom(rawValue: 200) {
print(unitCase)
}
The premise of this enum is a bit confusing to me but this is what you're looking for.

Dynamically initialize enum based on associated value

This is my enum:
enum E {
case a(Int), b(String)
}
The enum's associated value types are unique and always exactly one.
Let's say I have this variable:
let myInt = 0
I want to create an instance of E, based on variable myInt, dynamically. This should result in:
E.a(0)
But in the 'real world', I don't know what property I get. I only know one thing: I can initialize enum E with it. I need to dynamically initialize the enum, based on a property value. I currently have a huge switch on the property to initialize the enum, I don't want that.
But I have no idea how to accomplish this task. I tried mirroring the enum type, but I get a complex type and I have no idea how to proceed initializing it even if I know the types.
So I get a property of a certain type. I know that certain type matches a case in enum E, because there is exactly one case which associated value corresponds to the property type. I want to initialize an instance of that enum with that case, with the value of the property.
If your only starting point is the type of what will eventually be an associated value, you can use a switch statement:
enum E {
case a(Int)
case b(String)
init(associatedValue: Any) {
switch associatedValue {
case is Int:
self = .a(associatedValue as! Int)
case is String:
self = .b(associatedValue as! String)
default:
fatalError("Unrecognized type!")
}
}
}
let response = E(associatedValue: 1) // .a(1)
let other = E(associatedValue: "haha!") // .b("haha!")
The problem here is this switch must be exhaustive, meaning cover all types. So you either need a dumping ground case (.unreachable(Any)) or a fatalError so you can catch these in development.
You can use a custom initializer: (I have used more descriptive names)
enum TypeFinder {
case int(Int)
case string(String)
case unknown(Any)
init(value: Any) {
switch value {
case let v as Int: self = .int(v)
case let v as String: self = .string(v)
default: self = .unknown(value)
}
}
}
Testing:
var unknownTypeValue: Any = "Testing.."
print(TypeFinder(value: unknownTypeValue))
unknownTypeValue = 1234
print(TypeFinder(value: unknownTypeValue))
unknownTypeValue = true
print(TypeFinder(value: unknownTypeValue))
I believe you can do something like that
enum E: ExpressibleByStringLiteral, ExpressibleByIntegerLiteral {
case a(Int), b(String)
init(stringLiteral value: StringLiteralType) {
self = .b(value)
}
init(integerLiteral value: IntegerLiteralType) {
self = .a(value)
}
}

Enums and Generics in Swift

EDITED
Hello I trying to make my own Unit Converter
but there is some problem when i try to make both Weight and Length
There are so many duplicated codes
enum LengthUnit: String{
case inch
case cm
case m
case yard
static func unit(of value: String) -> LengthUnit?{
switch value {
case let value where value.contains("inch"):
return .inch
case let value where value.contains("cm"):
return .cm
case let value where value.contains("m"):
return .m
case let value where value.contains("yard"):
return .yard
default:
return nil
}
}
}
enum WeightUnit:String {
case g
case kg
case lb
case oz
static func unit(of value: String) -> WeightUnit?{
switch value {
case let value where value.contains("g"):
return .g
case let value where value.contains("kg"):
return .kg
case let value where value.contains("lb"):
return .lb
case let value where value.contains("oz"):
return .oz
default:
return nil
}
}
}
Not only get unit from String function but also many associated functions for converting, there are duplicated codes
So I try to implement it by generics but I don't have any idea of it
How can use enums and generics for both Unit types
Since you inherit your enums from String you're getting init?(rawValue: String) parsing initializer for free. Personally, I wouldn't create function like unit(of:) because it just throw away the amount part. Instead, I would create parse function like parse(value: String) -> (Double, LengthUnit)?
Anyway, if you really want unit(of:) function and want to reduce code duplication as much as possible you may indeed benefit from using generics.
First of all, we need Unit marker protocol like this
protocol UnitProtocol { }
Then, we can create generic function which will use init?(rawValue: String) of RawRepresentable Units to return unit based on passed string
func getUnit<U: UnitProtocol & RawRepresentable>(of value: String) -> U? where U.RawValue == String {
// you need better function to split amount and unit parts
// current allows expressions like "15.6.7.1cm"
// but that's question for another topic
let digitsAndDot = CharacterSet(charactersIn: "0123456789.")
let unitPart = String(value.drop(while: { digitsAndDot.contains($0.unicodeScalars.first!) }))
return U.init(rawValue: unitPart)
}
And thats essentially it. If you don't like to use functions and prefer static methods instead, then you just need to add these methods and call getUnit(of:) inside
enum LengthUnit: String, UnitProtocol {
case inch
case cm
case m
case yard
static func unit(of value: String) -> LengthUnit? {
return getUnit(of: value)
}
}
enum WeightUnit: String, UnitProtocol {
case g
case kg
case lb
case oz
static func unit(of value: String) -> WeightUnit? {
return getUnit(of: value)
}
}
Or, instead adding unit(of:) methods everywhere we may even do better and add extension
extension UnitProtocol where Self: RawRepresentable, Self.RawValue == String {
static func unit(of value: String) -> Self? {
return getUnit(of: value)
}
}
Now you'll get static unit(of:) for free by just adding conformance to String and Unit
enum WeightUnit: String, UnitProtocol {
case g
case kg
case lb
case oz
}
You can do this as a one-liner since the raw value of the enum item here is the string version of the enum item, so LengthUnit.inch --> "inch"
extension String {
var lengthUnit: LengthUnit? {
get {
return LengthUnit(rawValue:self)
}
}
}
Update
Updated version that contains an example of removing the number part of the string. What is the best solution for this is hard to know without knowing what kind of data to expect.
extension String {
var lengthUnit: LengthUnit? {
get {
let string = self.trimmingCharacters(in: CharacterSet(charactersIn: "01234567890."))
return LengthUnit(rawValue:string)
}
}
}
Comment: since you're creating a unit converter you are going to need to split your string into value and unit at some point anyway to be able to perform the conversion so it might be smarter to do that first and use my original version.
Comment 2: I don't see how you can possible make use of generics here, your input is always a String and I see no gain in having a function/property return a generics. You could simplify your design using only one Unit enum and then only having one unit property rather than two

Compound switch cases: may we have a single common value binding for compound enum cases that have the same type of associated value?

(As I prepared and almost finished writing up the question, re-reading the appropriate language guide section answered it for me, but possibly the Q&A can be of use for others, so I'll post it nonetheless)
Background
Consider the following enum, with cases that have one of two different types of associated values, Int or String:
enum Foo {
case bar(Int)
case baz(Int)
case bax(Int)
case fox(String)
}
When performing pattern matching in a switch statement, we may construct compound cases, each covering several possible matching patterns (entering the case branch if any of the patterns match):
func foo(_ foo: Foo) -> Int {
switch foo {
case .bar, .baz, .bax: return 42
case .fox: return 0
}
}
Just like non-compound cases, compound cases may also include value binding:
func foo(_ foo: Foo) -> Int {
switch foo {
case .bar(let x), .baz(let x), .bax(let x): return x
case .fox(let y): return Int(y) ?? 0
}
}
// or
func foo(_ foo: Foo) -> Int {
switch foo {
case let .bar(x), let .baz(x), let .bax(x): return x
case let .fox(y): return Int(y) ?? 0
}
}
Question
Is it possible to use a single common value binding for compound cases, which covers the compound of several enum cases that have the same type of associated value?
E.g., in the latter value binding examples above, some way to use a single binding functionality for the common-type associated value in the compound case
// not valid
func foo(_ foo: Foo) -> Int {
switch foo {
case .bar, .baz, .bax, (let x): return x
case .fox: return 0
}
}
No, this is not possible; in the value binding examples above, x must be bound in every pattern, and this must hold separately for every pattern in the compound cases.
Quoting the Language Guide - Control Flow [emphasis mine]
Compound cases can also include value bindings. All of the patterns of
a compound case have to include the same set of value bindings, and
each binding has to get a value of the same type from all of the
patterns in the compound case. This ensures that, no matter which part
of the compound case matched, the code in the body of the case can
always access a value for the bindings and that the value always has
the same type.
I we try to omit the binding in one of the patterns in the compound example above, we are given quite an self-explanatory error message on the subject:
func foo(_ foo: Foo) -> Int {
switch foo {
case .bar(_), .baz(let x), .bax(let x): return x
case .fox: return 0
}
}
error: 'x' must be bound in every pattern
This holds even if we don't use x in the body that follows
func foo(_ foo: Foo) -> Int {
switch foo {
case .bar(_), .baz(let x), .bax(let x): return 0
case .fox: return 0
}
} // same error

enum of non-literal values in Swift

Is there any way to map a non-literal value like tuple of dictionary to enums? Following code will throw Raw value for enum must be literal.
enum FileType {
case VIDEO = ["name": "Video", "contentTypeMatcher": "video/"]
case IMAGE = ["name": "Image", "contentTypeMatcher": "image/"]
case AUDIO = ["name": "Audio", "contentTypeMatcher": "aduio/"]
case PDF = ["name": "PDF", "contentTypeMatcher":"application/pdf"]
case TEXT = ["name": "Text", "contentTypeMatcher": "text/"]
case FOLDER= ["name": "Folder", "contentTypeMatcher" :"application/x-directory"]
case PLAIN = ["name": "Plain", "contentTypeMatcher": ""]
}
It's the same when I use tuples:
enum FileType {
case VIDEO = (name: "Video", contentTypeMatcher: "video/")
case IMAGE = (name: "Image", contentTypeMatcher: "image/")
case AUDIO = (name: "Audio", contentTypeMatcher: "aduio/")
case PDF = (name: "PDF", contentTypeMatcher:"application/pdf")
case TEXT = (name: "Text", contentTypeMatcher: "text/")
case FOLDER = (name: "Folder", contentTypeMatcher :"application/x-directory")
case PLAIN = (name: "Plain", contentTypeMatcher: "")
}
#Antonio gives workaround but does not answer the actual question.
Define your enum.
enum FileType {
case Image, Video
}
Give cases non-literal values, whatever type you want with conforming to RawRepresentable protocol. Do it by enum extension to have cleaner code.
extension FileType: RawRepresentable {
typealias Tuple = (name: String, contentTypeMatcher: String)
private static let allCases = [FileType.Image, .Video]
// MARK: RawRepresentable
typealias RawValue = Tuple
init?(rawValue: Tuple) {
guard let c = { () -> FileType? in
for iCase in FileType.allCases {
if rawValue == iCase.rawValue {
return iCase
}
}
return nil
}() else { return nil }
self = c
}
var rawValue: Tuple {
switch self {
case .Image: return Tuple("Image", "image/")
case .Video: return Tuple("Video", "video/")
}
}
}
To be able to match Tuple in switch, implement pattern matching operator.
private func ~= (lhs: FileType.Tuple, rhs: FileType.Tuple) -> Bool {
return lhs.contentTypeMatcher == rhs.contentTypeMatcher && lhs.name == rhs.name
}
And thats it...
let a = FileType.Image
print(a.rawValue.name) // "Image"
let b = FileType(rawValue: a.rawValue)!
print(a == b) // "true"
print(b.rawValue.contentTypeMatcher) // "image/"
Let's say I answered the question without questioning. Now... Enums (in Swift at least) are designed to have unique cases. Caveat to this workaround is that you can (I hope by accident) hold same rawValue for more cases. Generally your example code smells to me. Unless you (for very reasonable reason) need to create new enum value from tuple, consider redesign. If you want go with this workaround, I suggest (depends on project) to implement some check if all case raw values are unique. If not, consider this:
enum FileType {
case Video, Image
var name: String {
switch self {
case .Image: return "Image"
case .Video: return "Video"
}
var contentTypeMatcher: String {
switch self {
case .Image: return "image/"
case .Video: return "video/"
}
}
The language reference, when talking about Enumeration Declaration, clearly states that:
the raw-value type must conform to the Equatable protocol and one of the following literal-convertible protocols: IntegerLiteralConvertible for integer literals, FloatingPointLiteralConvertible for floating-point literals, StringLiteralConvertible for string literals that contain any number of characters, and ExtendedGraphemeClusterLiteralConvertible for string literals that contain only a single character.
So nothing else but literals can be used as raw values.
A possible workaround is to represent the dictionary as a string - for example, you can separate elements with commas, and key from value with colon:
enum FileType : String {
case VIDEO = "name:Video,contentTypeMatcher:video/"
case IMAGE = "name:Image,contentTypeMatcher:image/"
...
}
Then, using a computed property (or a method if you prefer), reconstruct the dictionary:
var dictValue: [String : String] {
var dict = [String : String]()
var elements = self.rawValue.componentsSeparatedByString(",")
for element in elements {
var parts = element.componentsSeparatedByString(":")
if parts.count == 2 {
dict[parts[0]] = parts[1]
}
}
return dict
}
My coworkers and I have been debating this topic recently as Swifts enum type is unique from other languages. In a language like Java where an enum is just a class that inherits from Enumeration, you can have static non-literal values assigned to each case.
In swift, we can not find a supported way to do this. From Swift documentation:
If a value (known as a “raw” value) is provided for each enumeration case, the value can be a string, a character, or a value of any integer or floating-point type.
Alternatively, enumeration cases can specify associated values of any type to be stored along with each different case value, much as unions or variants do in other languages. You can define a common set of related cases as part of one enumeration, each of which has a different set of values of appropriate types associated with it.
The second paragraph may seem like it can do what #Antonio asked but it is not. In swift's example:
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
But each enum is an instance with different value types (tuple vs string) and the values within them are different based on each instance of the enum created.
I wanted something that allowed more than the limited raw values but each enum contained the same value type (ie tuple, object, etc...) and is static.
With my coworkers input we came up with two options that have different tradeoffs.
The first is a private static dictionary by the enum that holds the value type you desire:
enum FooBarDict {
case foo
case bar
private static let dict = [foo: (x: 42, y: "The answer to life, the universe, and everything"),
bar: (x: 420, y: "Party time")]
var x: Int? { return FooBarDict.dict[self]?.x }
var y: String? { return FooBarDict.dict[self]?.y }
}
Our issue with this implementation is that there's no way at compile time that you can ensure that the developer has exhaustively included all of the enum cases. This means that any properties you right must be optional or return a default.
To resolve that issue we came up with the following:
enum FooBarFunc {
case foo
case bar
typealias Values = (x: Int, y: String)
private func getValues() -> Values {
switch self {
case .foo: return (x: 42, y: "The answer to life, the universe, and everything")
case .bar: return (x: 420, y: "Party time")
}
}
var x: Int { return getValues().x }
var y: String { return getValues().y }
}
Now it is exhaustive due to the switch statement in the getValues! A developer can not add a new case and compile without explicitly adding the value type.
My (perhaps unfounded) fear with this approach is that it may be both slower due to the switch statement lookup - although this may be optimized to be as fast as the dictionary lookup. And I am unsure if it will create a new value each time a enum property is requested. I'm sure I could find answers to both of these concerns but I've already wasted too much time on it.
To be honest, I hope I'm just missing something about the language and this is easily done in another way.