38

How do I use the Comparable protocol in Swift? In the declaration it says I'd have to implement the three operations <, <= and >=. I put all those in the class but it doesn't work. Also do I need to have all three of them? Because it should be possible to deduce all of them from a single one.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Kametrixom
  • 14,673
  • 7
  • 45
  • 62

3 Answers3

67

The Comparable protocol extends the Equatable protocol -> implement both of them

In Apple's Reference is an example from Apple (within the Comparable protocol reference) you can see how you should do it: Don't put the operation implementations within the class, but rather on the outside/global scope. Also you only have to implement the < operator from Comparable protocol and == from Equatable protocol.

Correct example:

class Person : Comparable {
    let name : String

    init(name : String) {
        self.name = name
    }
}

func < (lhs: Person, rhs: Person) -> Bool {
    return lhs.name < rhs.name
}

func == (lhs: Person, rhs: Person) -> Bool {
    return lhs.name == rhs.name
}

let paul = Person(name: "Paul")
let otherPaul = Person(name: "Paul")
let ben = Person(name: "Ben")

paul > otherPaul  // false
paul <= ben       // false
paul == otherPaul // true
hashier
  • 4,670
  • 1
  • 28
  • 41
Kametrixom
  • 14,673
  • 7
  • 45
  • 62
  • How do I do this in playground? – Van Du Tran Nov 16 '14 at 22:59
  • 4
    I don't get how this is considered 'implemented' when the actual code isn't in the class at all... Shouldn't the == < and all the rest be inside the class? Yet when I try that I get errors.. Do you know what I'm failing to understand? – fjlksahfob Nov 26 '14 at 15:06
  • 5
    @fjlksahfob the reason is because it's adding an extension to the operator at a scope outside the class. You could even do something like `func == (lhs: ClassA, rhs: ClassB) -> Bool` if you wanted, to compare two different classes (from testing it appears to only work in the direction specified, so you would also have to create `func == (lhs: ClassB, rhs: ClassA) -> Bool`). In other words, the Swift compiler interprets `(X == Y)` as `==(X, Y)` and not `X.==(Y)` – Oliver Mar 17 '15 at 04:31
  • When you ctrl-click `Comparable` the definition tells you need to implement `==`, `>=` and `>` which leads to a compile time error. I guess this is a documentation error from Apple? Your way works. I submitted a bug report. – qwerty_so Apr 14 '15 at 12:37
  • Finally the first language that provides a Comparable interface with operator overloading. It's annoying to have both operator overloading and CompareTo methods in C# and have to use the pesky CompareTo all the time. – Pavel Jul 20 '15 at 10:14
  • @paulpaul1076 Swift isn't the first one for sure, Haskell for example, which has also influenced Swift a lot. I get your point though, it's really nice to have custom operator overloading :D – Kametrixom Jul 20 '15 at 10:20
  • 1
    @Kametrixom, oh, cool, I haven't used Haskell, so I don't know, thanks for enlightening me. And C# does have custom operator overloading, but for sorting you use the IComparable interface, and compare by doing a.CompareTo(b) < 0, which means a < b. Why not just Make IComparable have the < operator instead? That was my point. Swift looks nice. – Pavel Jul 20 '15 at 10:31
  • 1
    In Swift 5, I found that I didn't need to conform to equatable or implement `==` when conforming to Comparable. I implemented only `<`, and was still able to test for equality. Is it just being clever about re-using `<` with swapped arguments? – Barry Jones Jan 10 '20 at 19:10
  • What if we do not implement == from Equatable protocol? In the documentation, Apple says that we need to conform to the Equatable protocol and Comparable protocol, but when I test in the playground without Equatable I can test >= operator ?! – Hrvoje Feb 11 '22 at 09:34
7

Here is an update of Kametrixom's answer for Swift 3:

class Person : Comparable {

    let name : String

    init(name : String) {
        self.name = name
    }    

    static func < (lhs: Person, rhs: Person) -> Bool {
        return lhs.name < rhs.name
    }

    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name
    }
}

Instances of the Person class can then be compared with the relational operators as follows:

let paul = Person(name: "Paul")
let otherPaul = Person(name: "Paul")
let ben = Person(name: "Ben")

print(paul > otherPaul)  // false
print(paul <= ben)       // false
print(paul == otherPaul) // true
Adil Hussain
  • 30,049
  • 21
  • 112
  • 147
Ekra
  • 3,241
  • 10
  • 41
  • 61
0

To implement Swift's Comparable protocol, you need to conform to the Equatable protocol first by implementing static func == (lhs: Self, rhs: Self) -> Bool, then implementing the only required function static func < (lhs: Self, rhs: Self) -> Bool for Comparable.

Instead of declaring global operator overloads, you should instead implement the protocol conforming methods within the struct/class itself. Although global operator overloads satisfy the protocol conformance, it's bad practice to declare them that way instead of the intended static methods on the struct/class.

If you look at the documentation example, you will see that the same is shown as sample code.

I would instead write the following:

class Person: Comparable {
    let name: String

    init(name: String) {
        self.name = name
    }

    static func < (lhs: Person, rhs: Person) -> Bool {
        return lhs.name < rhs.name
    }

    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name
    }
}

or even separate out the protocol conformance out of the class declaration like so:

class Person {
    let name: String

    init(name: String) {
        self.name = name
    }
}

extension Person: Comparable {
    static func < (lhs: Person, rhs: Person) -> Bool {
        return lhs.name < rhs.name
    }

    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name
    }
}

which would probably be closer to production level code.

Schemetrical
  • 5,506
  • 2
  • 26
  • 43