98
votes

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

6
You can reduce the Jack, Queen, King, Ace cases in the == operator implementation to just: case (let x, let y) where x == y: return trueAlexander

6 Answers

84
votes

Edit: As Etan points out, you can omit the (_) wildcard match to use this more cleanly.


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.

33
votes

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
}
24
votes

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

4
votes

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!")
}
1
votes

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!

0
votes

You don't need func == or Equatable. Just use an enumeration case pattern.

let rank = CardRank.Ace
if case .Ace = rank { print("Snoopy") }