Swift Enum Inequality

Swift enum inequality

Wouldn't something along these lines suffice?

enum State: Int, Comparable {
case Loading
case Loaded
case Resolved
case Processed
}

func < (lhs: State, rhs: State) -> Bool {
return lhs.rawValue < rhs.rawValue
}

let state = State.Resolved
state > .Loaded // true

Note that only < implementation is needed since enums are already equatable...

And in general, comparability of enums is independent of their raw values, if any – for example:

enum State: Comparable {
case Good
case Bad
}

func < (lhs: State, rhs: State) -> Bool {
return lhs == .Bad && rhs == .Good
}

let state = State.Good
state > .Bad // true

On the second thought, Swift does allow us to extend RawRepresentable protocol with exact effect that @devios is looking for:

/// Extends all `RawRepresentable` enums with `Comparable` raw values, 
/// such as `enum E : Int` or `enum E : String`...
///
public func < <E: RawRepresentable where E.RawValue : Comparable> (lhs: E, rhs: E) -> Bool {
return lhs.rawValue < rhs.rawValue
}

With this tucked in somewhere in your library of extensions, all you need to do is to explicitly opt in by declaring your type as Comparable:

enum N: Int, Comparable {
case Zero, One, Two, Three
}

enum S: String, Comparable {
case A, B, C, D
}

let n: N = .Two
n > .One // true

let ss: [S] = [.B, .A, .D, .C].sort() // [A, B, C, D]

This still allows you to provide a concrete implementation if the generic behaviour is not a perfect fit for a particular type:

func < (lhs: S, rhs: S) -> Bool {
return rhs.hashValue < lhs.hashValue // inverting the ordering
}

let ss: [S] = [.B, .A, .D, .C].sort() // [D, C, B, A]

How to test equality of Swift enums with associated values

Swift 4.1+

As @jedwidz has helpfully pointed out, from Swift 4.1 (due to SE-0185, Swift also supports synthesizing Equatable and Hashable for enums with associated values.

So if you're on Swift 4.1 or newer, the following will automatically synthesize the necessary methods such that XCTAssert(t1 == t2) works. The key is to add the Equatable protocol to your enum.

enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

Before Swift 4.1

As others have noted, Swift doesn't synthesize the necessary equality operators automatically. Let me propose a cleaner (IMHO) implementation, though:

enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}

public func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
switch (lhs, rhs) {
case let (.Name(a), .Name(b)),
let (.Number(a), .Number(b)):
return a == b
default:
return false
}
}

It's far from ideal — there's a lot of repetition — but at least you don't need to do nested switches with if-statements inside.

How to do an if-else comparison on enums with arguments

The trick is to not actually check with == but rather use the case keyword in conjunction with a single = in your if statement. This is a little counter intuitive in the beginning but just like if let, you get used to it pretty fast:

enum Normal {
case one
case two, three
}

enum NormalRaw: Int {
case one = 1
case two, three
}

enum NormalArg {
case one(Int)
case two, three
}

let normalOne = Normal.one
let normalRawOne = NormalRaw.one
let normalArgOne = NormalArg.one(1)

if case .one = normalOne {
print("A normal one") //prints "A normal one"
}

if case .one = normalRawOne {
print("A normal \(normalRawOne.rawValue)") //prints "A normal 1"
}

if case .one(let value) = normalArgOne {
print("A normal \(value)") //prints "A normal 1"
}

The point is that in Swift you only get equation of enums for free if your enum uses a raw type or if you have no associated values (try it out, you can't have both at the same time). Swift however does not know how to compare cases with associated values - I mean how could it? Let's look at this example:

Normal.one == .one //true
Normal.one == .two //false

NormalRaw.one == .one //true
NormalRaw.one == .two //false

NormalArg.one(1) == .one(1) //Well...?
NormalArg.one(2) == .one(1) //Well...?
NormalArg.one(1) == .two //Well...?

Maybe this makes it clearer why this cannot work out of the box:

class Special {
var name: String?
var special: Special?
}

enum SpecialEnum {
case one(Special)
case two
}

var special1 = Special()
special1.name = "Hello"

var special2 = Special()
special2.name = "World"
special2.special = special1

SpecialEnum.one(special1) == SpecialEnum.one(special2) //Well...?

So if you want enums with associated values, you'll have to implement Equatable protocol in your enum by yourself:

enum NormalArg: Equatable {
case one(Int)
case two

static func ==(lhs: NormalArg, rhs: NormalArg) -> Bool {
switch (lhs, rhs) {
case (let .one(a1), let .one(a2)):
return a1 == a2
case (.two,.two):
return true
default:
return false
}
}
}

How to compare enum with associated values by ignoring its associated value in Swift?

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.

equality operator overloading in swift enums with associated values

In your implementation,

if (a == b) {

recursively calls the same == function again. This eventually crashes with a stack overflow.

A working implementation would for example be:

func ==(a: MyEnum, b: MyEnum) -> Bool {
switch (a,b) {
case (.Function(let aa), .Function(let bb)):
return aa == bb
case (.Error, .Error):
return true
case (.None, .None):
return true
default:
return false
}
}

Is it possible for a Swift enum with Comparable raw values to synthesize the Comparable protocol implementations automatically?

You just need to give a default implementation of the < operator for RawRepresentable when its associated RawValue is Comparable:

extension RawRepresentable where RawValue : Comparable {
static func < (lhs: Self, rhs: Self) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}

enum RoyalRank: UInt8, Comparable {
case emperor = 3
case king = 2
case duke = 1
}

let e = RoyalRank.emperor
let k = RoyalRank.king
print(e > k)

Add Comparableto your enum, and voilà!

Testing for enum value fails if one has associated value?

Enumerations are automatically Equatable when they have a raw value that's Equatable. In your first case, the raw value is assumed to be Int, but it would work if you'd given it another specific type like UInt32 or even String.

Once you add an associated value, however, this automatic conformance with Equatable doesn't happen any more, since you can declare this:

let littleNorth = CompassPoint.North(2)
let bigNorth = CompassPoint.North(99999)

Are those equal? How should Swift know? You have to tell it, by declaring the enum as Equatable and then implementing the == operator:

enum CompassPoint : Equatable {
case North(Int)
case South
case East
case West
}

public func ==(lhs:CompassPoint, rhs:CompassPoint) -> Bool {
switch (lhs, rhs) {
case (.North(let lhsNum), .North(let rhsNum)):
return lhsNum == rhsNum
case (.South, .South): return true
case (.East, .East): return true
case (.West, .West): return true
default: return false
}
}

Now you can test for equality or inequality, like this:

let otherNorth = CompassPoint.North(2)
println(littleNorth == bigNorth) // false
println(littleNorth == otherNorth) // true

Equality on Objective-C typedef enum in Swift

You could unwrap the enum and constants with '.value' to get the underlying integer, which should be switchable:

switch x.value {
case Foo.value:
}

Maybe this is a bug and apple fix it in future releases.

Compiler error when comparing values of enum type with associated values?

As you said in a comment, your enumeration type actually has associated
values. In that case there is no default == operator for the enum type.

But you can use pattern matching even in an if statement (since Swift 2):

class MyClass {
enum MyEnum {
case FirstCase
case SecondCase
case ThirdCase(Int)
}

var state:MyEnum!

func myMethod () {
if case .FirstCase? = state {

}
}
}

Here .FirstCase? is a shortcut for .Some(MyEnum.FirstCase).

In your switch-statement, state is not automatically unwrapped,
even if it is an implicitly unwrapped optional (otherwise you could
not match against nil). But the same pattern can be used here:

switch state {
case .FirstCase?:
// do something...
default:
break
}

Update: As of Swift 4.1 (Xcode 9.3) the compiler can synthesize conformance to Equatable/Hashable for enums with associated values (if all their types are Equatable/Hashable). It suffices to declare the conformance:

class MyClass {
enum MyEnum: Equatable {
case firstCase
case secondCase
case thirdCase(Int)
}

var state:MyEnum!

func myMethod () {
if state == .firstCase {
// ...
}
}
}


Related Topics



Leave a reply



Submit