Swift Protocol Implements Equatable
Asked Answered
D

2

34

I have the the below Protocol:

protocol Cacheable {
    //....//
    func identifier() -> String
}

Can I make Cacheable implements Equatable ?

when I do the following:

extension Cacheable: Equatable {}

func ==(lhs:Cacheable,rhs:Cacheable) -> Bool {

     return lhs.identifier() == rhs.identifier()
}

I got this error message : Extension of protocol Cacheable cannot have an inheritance clause

Decoupage answered 10/8, 2015 at 14:16 Comment(0)
B
47

1) Allow two Cacheables of the same type to be compare

protocol Cacheable: Equatable {
    //....//
    func identifier() -> String
}

func ==<T : Cacheable>(lhs: T, rhs: T) -> Bool {
    return lhs.identifier() == rhs.identifier()
}

Pros

This is the simplest solution.

Cons

You can only compare two Cacheable objects of the same type. This means that code below will fail and in order to fix it you need to make Animal conform to Cacheable:

class Animal {

}

class Dog: Animal,Cacheable {
    func identifier() -> String {
        return "object"
    }
}

class Cat: Animal,Cacheable {
    func identifier() -> String {
        return "object"
    }
}

let a = Dog()

let b = Cat()

a == b //such comparison is not allowed

2) Allow Cacheables of any type to be compared

protocol Cacheable:Equatable {
    //....//
    func identifier() -> String
}

func ==<T:Cacheable>(lhs: T, rhs: T) -> Bool {
    return lhs.identifier() == rhs.identifier()
}

func !=<T:Cacheable>(lhs: T, rhs: T) -> Bool {
    return lhs.identifier() != rhs.identifier()
}

func ==<T:Cacheable, U:Cacheable>(lhs: T, rhs: U) -> Bool {
    return lhs.identifier() == rhs.identifier()
}

func !=<T:Cacheable, U:Cacheable>(lhs: T, rhs: U) -> Bool {
    return lhs.identifier() != rhs.identifier()
}

Pros

Removes limitations described above for solution 1. Now you can easily compare Dog and Cat.

Cons

  • Implementation is longer. Actually I am not sure why specifying only == functions is not sufficient - this might be a bug with a compiler. Anyway, you have to provide the implementation for both == and !=.
  • In some cases the benefit of this implementation may also pose a problem as you are allowing the comparison between absolutely different objects and compiler is totally OK with it.

3) Without conforming to Equatable

protocol Cacheable {
    //....//
    func identifier() -> String
}

func ==(lhs: Cacheable, rhs: Cacheable) -> Bool {
    return lhs.identifier() == rhs.identifier()
}

func !=(lhs: Cacheable, rhs: Cacheable) -> Bool {
    return lhs.identifier() != rhs.identifier()
}

Pros

You can use Cacheable as type without needing any generics. This introduces a whole new range of possibilities. For example:

let c:[Cacheable] = [Dog(),RaceCar()]

c[0] == c[1]
c[0] != c[1]

With solutions 1 and 2 such code would fail and you would have to use generics in your classes. However, with the latest implementation Cacheable is treated as a type, so you are allowed to declare an array of type [Cacheable].

Cons

You no longer declare conformance to Equatable so any functions which accept Equatable parameters will not accept Cacheable. Obviously, apart from == and != as we declared them for Cacheables.

If this is not a problem in your code I would actually prefer this solution. Being able to treat protocol as a type is super useful in many cases.

Bynum answered 10/8, 2015 at 14:26 Comment(12)
I did before and I got : Binary operator '==' cannot be applied to two Cacheable operands when trying to compare two object implements CacheableDecoupage
That's why I used generics ;) This code works. I have tested it right now with Xcode 7 beta 5Bynum
Don't use playground. Try putting it in code. Drop it in AppDelegate of one of your apps and check it in the willFinishLaunching functionBynum
This should be accepted answer. Works perfectly in swift 2.1Mammon
Doesn't work. I think that's because both lhs and rhs might be of different types and swift doesn't have a way of telling whether or not you are checking different types. Example: Type1() and Type2 both conform to Cacheable. Now trying Type1() as Cacheable == Type2() as Cacheable -> Error. Cannot be applied to two operands of type Cacheable.Sino
yep. That's what I was saying. They have to be of the same type for it to work. I wanted to make the == on the protocol type. I didn't care if they were different objects as long as they had the same properties I wanted to consider them the same.Sino
I ended up creating a "isEqualTo(protocol)" to have this behaviour. Not perfect but I prefer that than adding a class in the middle to haven them both be of the same common type.Sino
@NunoGonçalves can you check my updated answer to see if any of the alternative solutions fits your needs?Bynum
That's it! Changing the "==" implementation to have two types works. :) Of course... Thanks!Sino
@NunoGonçalves have you checked the 3rd solution? In my view it is by far the simplest one, although I might be missing some use case when conformance to Equatable is extremely necessary :)Bynum
Note that with #2, once Cachable extends Equatable, it inherits a Self reference which means it can no longer be used as a type. ie. let x:Cachable = ... becomes invalid. This is a huge issue with Swift's protocols and can be difficult, if not impossible to hack around depending on what you are doing.Whenever
You're right. I'm mistaken. I don't know what I did. Sorry.Venosity
S
11

Try.

extension Equatable where Self : Cacheable {
}
Solutrean answered 10/8, 2015 at 14:23 Comment(2)
This seems not to work: I defined a class class MyClass : Cacheable { ... } and tried to pass an instance of that class to a function func foo<T : Equatable>(obj : T) and that did not compile. (Btw.: An identical solution was posted and deleted later because it does not work.)Rare
same, getting Binary operator '==' cannot be applied to two 'SubtitleTrack' operands, where SubtitleTrack is a protocol using this extension :(Suborbital

© 2022 - 2024 — McMap. All rights reserved.