Enums with associated values are a very powerful tool in Swift. You can store context sensitive information along with the value of the enum itself without the need of an additional data structure. But, as of in Swift 3.1, it has some serious limitations. For example, comparisons of enums are not that easy. Let’s look at this simple code:
1 2 3 4 5 6 7 8 9 |
enum Happiness1 { case Sad case Happy } var mood1 = Happiness1.Sad if mood1 == Happiness1.Sad { Swift.print("1. I'm quite sad today!") } |
That’s easy and it works as expected. But what happens, if we want to put some grade of happiness in the enum?
1 2 3 4 5 6 7 8 9 |
enum Happiness2 { case Sad case Happy(ofGrade: Int) } var mood2 = Happiness2.Sad if mood2 == Happiness2.Sad { Swift.print("2. I'm quite sad today!") } |
The compiler gives an error
Enums.playground:19:10: note: binary operator '==' cannot be synthesized for enums with associated values
if mood == Happiness2.Sad {
even though we did not compare our value with the enum member with the associated value. „Too bad, so sad“, as Donald Trump would say. The only possibility to compare those enums is via the switch construction. But wait, that we can put in the enum itself. We can make the enum „equatable“. Here is the solution:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
enum Happiness3: Equatable { case Sad case Happy(ofGrade: Int) static func == (lhs: Happiness3, rhs: Happiness3) -> Bool { switch lhs { // look at the left hand side case .Sad: // its sad switch rhs { // and what's on the right? case .Sad: // also sad return true default: // something else break } case .Happy(let lgrade): // its happy with some grade switch rhs { // look on the right hand side case .Happy(let rgrade): // also happy return lgrade == rgrade // return true if grades are the same default: break } } return false // otherwise return false } } var mood3 = Happiness3.Sad if mood3 == Happiness3.Sad { Swift.print("3. I'm quite sad today!") } |
Now everything’s fine. I admit, the comparison function in the enum is quite lengthy and ugly and its getting worse, the more members you have in the enum. But this is the only way I know of, to keep your logic code clean and simple. Now you can even write something like this:
1 2 3 |
if mood3 != Happiness3.Happy(ofGrade: 5) { Swift.print("3. Not that happy today!") } |
OK, you can get it shorter by using Swift’s ability of the switch construct to act on tuples as stated in some other blogs (e.g. by Ole Begemann). Instead of nested switch statements we can test on both sides in one switch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
enum Happiness4: Equatable { case Sad case Happy(ofGrade: Int) static func == (lhs: Happiness4, rhs: Happiness4) -> Bool { switch (lhs, rhs) { // look at both sides simultaneously case (.Sad, .Sad): return true // both are sad // both happy: return true if grades are same case (.Happy(let lgrade), .Happy(let rgrade)): return lgrade == rgrade // for all other combinations: return false case (.Sad, _), (.Happy(_), _): return false } } } var mood4 = Happiness4.Sad if mood4 == Happiness4.Sad { Swift.print("4. I'm quite sad today!") } |
We use the “_” for those terms we are not interested in (in the sense of „any“). We did not use a default case on purpose. With a default case we would not get warned if we would extend the enum with more members without the extension of the switch statement.
Somehow it’s a matter of taste which version you prefer. The second one is a little bit shorter but maybe harder to read.
Here is the playground for this simple example: