Making a protocol equatable

This morning I was playing with a couple of model objects, trying to find a way to make them conform to Equatable. That’s not a big deal, you might say… well, in this case I wanted to make the protocol those objects implement, not the actual objects, conform to Equatable, so things got a little tricky.

Let’s say this is where I started:

protocol Drinkable {
    var name: String { get }
    var alcohol: Float { get }
}

struct Beer: Drinkable {
    let name: String
    let alcohol: Float
}

Now, I want everything that conforms to Drinkable to be Equatable. So this was my first attempt:

extension Drinkable: Equatable { }

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

let guiness = Beer(name: "Guiness", alcohol: 6)
let anotherGuiness = Beer(name: "Guiness", alcohol: 6)

let singha = Beer(name: "Singha", alcohol: 5)

print(guiness == anotherGuiness)
print(guiness == singha)

Nah-ha. “Extension of protocol ‘Drinkable’ cannot have an inheritance clause”

The solution? Turning things around little bit (would this qualify as “inverting the dependency”, haha-lol?) and enforce Equatable to respond to == when the type implementing Equatable is Drinkable. In other words…

extension Equatable where Self: Drinkable {}

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

Ta-dahhhhh!

Here is a playground, just for you: Protocol_Equatable.playground

5 Replies to “Making a protocol equatable”

  1. Hi Cesar !
    I’m in no way a Swift expert, but either there is a huge thing I can’t even see or the extension actually does not add anything.
    With the extension commented out the Playground’s results are identical.

    Moreover, Beer still does not conform to Equatable.
    ‘func == (lhs: Drinkable, rhs: Drinkable)’ just provides the ability to ‘compare’ (with == operator) two Drinkable but it does not make the Drinkable-conforming types Equatable.

    func takesAnEquatable(param: T) {
    print(param)
    }
    takesAnEquatable(guiness) // error: cannot invoke ‘takesAnEquatable’ with an argument list of type ‘(Beer)’

    As opposed to a type that does conform to Equatable:
    struct Water: Drinkable, Equatable {
    let name: String
    let alcohol: Float
    }
    func == (lhs: Water, rhs: Water) -> Bool { // Without that Water does not conform to Equatable
    return lhs.name == rhs.name && lhs.alcohol == rhs.alcohol
    }
    takesAnEquatable(evian) // prints: Water(name: “Evian”, alcohol: 0.0)

    ‘func == (lhs: Drinkable, rhs: Drinkable)’ just provides the ability to ‘compare’ two Drinkable, even with different types, regardless their Equatable-conforming.
    let evian = Water(name: “Evian”, alcohol: 0)
    print(evian == guiness) // false

    Equatable is about comparing two instances of the same type: func ==(_ lhs: Self, _ rhs: Self) -> Bool

  2. LOL – Dark Mode and I am getting black text on black background – so excuse any spellings!

    iOS13 and Swift 5.1 – it still works! Fantastic!

Leave a Reply

Your email address will not be published. Required fields are marked *