After reading How to test equality of Swift enums with associated values, I implemented the following enum:
enum CardRank {
case Number(Int)
case Jack
case Queen
case King
case Ace
}
func ==(a: CardRank, b: CardRank) -> Bool {
switch (a, b) {
case (.Number(let a), .Number(let b)) where a == b: return true
case (.Jack, .Jack): return true
case (.Queen, .Queen): return true
case (.King, .King): return true
case (.Ace, .Ace): return true
default: return false
}
}
The following code works:
let card: CardRank = CardRank.Jack
if card == CardRank.Jack {
print("You played a jack!")
} else if card == CardRank.Number(2) {
print("A two cannot be played at this time.")
}
However, this doesn't compile:
let number = CardRank.Number(5)
if number == CardRank.Number {
print("You must play a face card!")
}
... and it gives the following error message:
Binary operator '==' cannot be applied to operands of type 'CardRank' and '(Int) -> CardRank'
I'm assuming this is because it's expecting a full type and CardRank.Number
does not specify an entire type, whereas CardRank.Number(2)
did. However, in this case, I want it to match any number; not just a specific one.
Obviously I can use a switch statement, but the whole point of implementing the ==
operator was to avoid this verbose solution:
switch number {
case .Number:
print("You must play a face card!")
default:
break
}
Is there any way to compare an enum with associated values while ignoring its associated value?
Note: I realize that I could change the case in the ==
method to case (.Number, .Number): return true
, but, although it would return true correctly, my comparison would still look like its being compared to a specific number (number == CardRank.Number(2)
; where 2 is a dummy value) rather than any number (number == CardRank.Number
).
We had to do this because Swift doesn't allow us to have both: raw values and associated values within the same enum. A Swift enum can either have raw values or associated values. Why is that? It's because of the definition of a raw value: A raw value is something that uniquely identifies a value of a particular type.
In Swift enum, we learned how to define a data type that has a fixed set of related values. However, sometimes we may want to attach additional information to enum values. These additional information attached to enum values are called associated values.
Enumeration is a data type that allows you to define a list of possible values. An enum allows you to create a data type with those set of values so that they can be recognised consistently throughout your app.
Cast Int To Enum may be of some help. Go with the 2nd option. The 1st one can cause an exception if the integer is out of the defined range in your Enumeration. In current example I compare to 'magic number' but in real application I am getting data from integer field from DB.
Edit: As Etan points out, you can omit the (_)
wildcard match to use this more cleanly:
let number = CardRank.Number(5)
if case .Number = number {
// Is a number
} else {
// Something else
}
Unfortunately, I don't believe that there's an easier way than your switch
approach in Swift 1.2.
In Swift 2, however, you can use the new if-case
pattern match:
let number = CardRank.Number(5)
if case .Number(_) = number {
// Is a number
} else {
// Something else
}
If you're looking to avoid verbosity, you might consider adding an isNumber
computed property to your enum that implements your switch statement.
Unfortunately in Swift 1.x there isn't another way so you have to use switch
which isn't as elegant as Swift 2's version where you can use if case
:
if case .Number = number {
//ignore the value
}
if case .Number(let x) = number {
//without ignoring
}
In Swift 4.2 Equatable
will be synthesized if all your associated values conform to Equatable
. All you need to do is add Equatable
.
enum CardRank: Equatable {
case Number(Int)
case Jack
case Queen
case King
case Ace
}
https://developer.apple.com/documentation/swift/equatable?changes=_3
What I usually do to compare if two enum cases "match" no matter their associated value is:
I have a protocol Matchable
:
protocol Matchable {
static func ~= (lhs: Self, rhs: Self) -> Bool
}
Then I make enums conform to it:
extension CardRank: Matchable {
static func ~= (lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
case
(.number, .number),
(.jack, .jack),
(.queen, .queen),
(.king, .king),
(.ace, .ace):
return true
default:
return false
}
}
}
let card1: CardRank = .number(1)
let card2: CardRank = .number(2)
let card3: CardRank = .jack
print(card1 ~= card2) // true
print(card1 ~= card3) // false
Here's a simpler approach:
enum CardRank {
case Two
case Three
case Four
case Five
case Six
case Seven
case Eight
case Nine
case Ten
case Jack
case Queen
case King
case Ace
var isFaceCard: Bool {
return (self == Jack) || (self == Queen) || (self == King)
}
}
There's no need to overload the == operator, and checking for card type does not require confusing syntax:
let card = CardRank.Jack
if card == CardRank.Jack {
print("You played a jack")
} else if !card.isFaceCard {
print("You must play a face card!")
}
I didn't want to conform Equatable
(it didn't help me either) and I wanted to filter for other cases than a specific one, so instead of simply writing card != .Number
I had to write the following. (I adjusted my code to this question.)
enum CardRank {
...
var isNumber: Bool {
if case .Number = self { return true }
return false
}
}
So I can write not a number in a complex condition:
if something && !card.isNumber { ... }
I wish I could just write card != .Number
, but the compiler was always complaining with Type of expression is ambiguous without more context. Maybe in an upcoming swift version!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With