Dynamically initialize enum based on associated value - swift

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)
}
}

Related

How can I get the property value enum?

I have an Enum with 2 cases and each take a String and an Int as properties:
public enum TestEnum {
case case1(String, Int? = nil)
case case2(String, Int? = nil)
}
I create an enum with value case1 and these 2 properties:
let e = TestEnum.case1("abc", 123)
my question is how can I get
I tried
let a = e.case1.0 // expect to get 'abc' back
let b = e.case1.1 // expect to get '123' back
print ("\(a)")
print ("\(b)")
But I get compile error 'Enum case 'case1' cannot be used as an instance member'
The type of the variables is TestEnum for both a and b. Which exact case the variable represents isn't encoded in the type. Because of this, you cannot access associated values of an enum case variable.
Instead, you need to use if case let to conditionally cast the enum case and assign its associated values to variables.
let a = TestEnum.case1("a", 1)
if case let .case1(string, int) = a {
print(string, int)
}
You can use pattern matching to access the values. (see patterns documentation)
Using switch:
switch e {
case .case1(let stringValue, let intValue):
print("stringValue: \(stringValue), intValue: \(intValue)")
default:
break
}
or if:
if case .case1(let stringValue, let intValue) = e {
print("stringValue: \(stringValue), intValue: \(intValue)")
}

Filter array of items by enum property with associated value

class MyClass: Decodable {
let title: String?
let type: MyClass.MyType?
enum MyType {
case article(data: [Article])
case link(data: [LinkTile])
case none
}
}
I would like to filter an array of MyClass items, so the filtered array won't contain instances with type .none
let filteredArray = array.filter { $0.type != .none } // this doesn't work
Unfortunately, you can't use == with enums with associated values. You need to use pattern matching, but that needs to be done in a switch or if statement.
So, that leads to something ugly like this:
let filteredArray = array.filter { if case .none = $0.type! { return false }; return true }
Notes:
You can't name your enum Type because it conflicts with the built-in Type. Change it to something like MyType.
It is terribly confusing to use none as a case in a custom enum because it gets confused (by the humans) with none in an optional. This is made worse by the fact that your type property is optional. Here I have force unwrapped it, but that is dangerous of course.
You could do:
if case .none? = $0.type
This would match the none case explicitly and treat nil as something you want to keep.
To filter out nil and .none, you could use the nil coalescing operator ??:
if case .none = ($0.type ?? .none)
I would suggest declaring type as MyClass.MyType instead of MyClass.MyType?.
I made you a simple example of how to use enum in your context with a filter function.
enum Foo {
case article(data: [Int])
case link(data: [String])
case `none`
static func myfilter(array: [Foo]) -> [Foo]{
var newArray:[Foo] = []
for element in array {
switch element {
case .article(let article):
newArray.append(.article(data: article))
case .link(let link):
newArray.append(.link(data: link))
case .none:
break
}
}
return newArray
}
}
let foo: [Foo] = [.article(data: [1,2,3]), .link(data: ["hello", "world"]), .none]
print(Foo.myfilter(array: foo))
I made a code which you can compile and test, you have to change the type for Foo, articleand link.
When you want to use an enum, you have to use switch case.
If you absolutely want to use the filter in swift you can, but you need to implement the protocol Sequence which is more complicated in this case.
For each case of your enum, you have to manage a case which use the concept of pattern matching. It is very powerful.

Extract associated value from enum regardless of the case in 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)

Can I change the Associated values of a enum?

I am reading the Swift tour document, and facing a problem.
Here is the code:
enum SimpleEnum {
case big(String)
case small(String)
case same(String)
func adjust() {
switch self {
case let .big(name):
name += "not"
case let .small(name):
name += "not"
case let .same(name):
name += "not"
}
}
}
The function adjust() won't work, I wonder if there is a way to change the Associated value of an enum, and how?
Your most immediate problem is that you're attempting to change the value of an immutable variable (declared with let) when you should be declaring it with var. This won't solve this particular problem though since your name variable contains a copy of the associated value, but in general this is something that you need to be aware of.
If you want to solve this, you need to declare the adjust() function as a mutating function and reassign self on a case by case basis to be a new enum value with an associated value composed from the old one and the new one. For example:
enum SimpleEnum{
case big(String)
case small(String)
case same(String)
mutating func adjust() {
switch self{
case let .big(name):
self = .big(name + "not")
case let .small(name):
self = .small(name + "not")
case let .same(name):
self = .same(name + "not")
}
}
}
var test = SimpleEnum.big("initial")
test.adjust()
switch test {
case let .big(name):
print(name) // prints "initialnot"
case let .small(name):
print(name)
case let .same(name):
print(name)
}

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.