Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Equatable on a protocol

I don't think this can be done but I'll ask anyway. I have a protocol:

protocol X {} 

And a class:

class Y:X {} 

In the rest of my code I refer to everything using the protocol X. In that code I would like to be able to do something like:

let a:X = ... let b:X = ... if a == b {...} 

The problem is that if I try to implement Equatable:

protocol X: Equatable {} func ==(lhs:X, rhs:X) -> Bool {     if let l = lhs as? Y, let r = hrs as? Y {         return l.something == r.something     }     return false }  

The idea to try and allow the use of == whilst hiding the implementations behind the protocol.

Swift doesn't like this though because Equatable has Self references and it will no longer allow me to use it as a type. Only as a generic argument.

So has anyone found a way to apply an operator to a protocol without the protocol becoming unusable as a type?

like image 896
drekka Avatar asked Feb 09 '17 06:02

drekka


People also ask

What is the difference between equitable and comparable protocol?

In Swift, there's the Equatable protocol, which explicitly defines the semantics of equality and inequality in a manner entirely separate from the question of identity. There's also the Comparable protocol, which builds on Equatable to refine inequality semantics to creating an ordering of values.

Does string conform to Equatable?

When a value is changed from old value, this notifies to subscribers, so this generics type should conform to Equatable protocol. But when a type is String? , Xcode raise Type 'String?' does not conform to protocol 'Equatable' error.

How do you declare a protocol in Swift?

Custom types state that they adopt a particular protocol by placing the protocol's name after the type's name, separated by a colon, as part of their definition. Multiple protocols can be listed, and are separated by commas: struct SomeStructure: FirstProtocol, AnotherProtocol { // structure definition goes here.

Are arrays Equatable Swift?

Swift 4.1 update: With the introduction of conditional conformance in Swift 4.1, Array now conforms to Equatable , so the issue should be resolved without the need to resort to any workarounds.


1 Answers

If you directly implement Equatable on a protocol, it will not longer be usable as a type, which defeats the purpose of using a protocol. Even if you just implement == functions on protocols without Equatable conformance, results can be erroneous. See this post on my blog for a demonstration of these issues:

https://khawerkhaliq.com/blog/swift-protocols-equatable-part-one/

The approach that I have found to work best is to use type erasure. This allows making == comparisons for protocol types (wrapped in type erasers). It is important to note that while we continue to work at the protocol level, the actual == comparisons are delegated to the underlying concrete types to ensure correct results.

I have built a type eraser using your brief example and added some test code at the end. I have added a constant of type String to the protocol and created two conforming types (structs are the easiest for demonstration purposes) to be able to test the various scenarios.

For a detailed explanation of the type erasure methodology used, check out part two of the above blog post:

https://khawerkhaliq.com/blog/swift-protocols-equatable-part-two/

The code below should support the equality comparison that you wanted to implement. You just have to wrap the protocol type in a type eraser instance.

protocol X {     var name: String { get }     func isEqualTo(_ other: X) -> Bool     func asEquatable() -> AnyEquatableX }  extension X where Self: Equatable {     func isEqualTo(_ other: X) -> Bool {         guard let otherX = other as? Self else { return false }         return self == otherX     }     func asEquatable() -> AnyEquatableX {         return AnyEquatableX(self)     } }  struct Y: X, Equatable {     let name: String     static func ==(lhs: Y, rhs: Y) -> Bool {         return lhs.name == rhs.name     } }  struct Z: X, Equatable {     let name: String     static func ==(lhs: Z, rhs: Z) -> Bool {         return lhs.name == rhs.name     } }  struct AnyEquatableX: X, Equatable {     var name: String { return value.name }     init(_ value: X) { self.value = value }     private let value: X     static func ==(lhs: AnyEquatableX, rhs: AnyEquatableX) -> Bool {         return lhs.value.isEqualTo(rhs.value)     } }  // instances typed as the protocol let y: X = Y(name: "My name") let z: X = Z(name: "My name") let equalY: X = Y(name: "My name") let unequalY: X = Y(name: "Your name")  // equality tests print(y.asEquatable() == z.asEquatable())           // prints false print(y.asEquatable() == equalY.asEquatable())      // prints true print(y.asEquatable() == unequalY.asEquatable())    // prints false 

Note that since the type eraser conforms to the protocol, you can use instances of the type eraser anywhere an instance of the protocol type is expected.

Hope this helps.

like image 119
Khawer Khaliq Avatar answered Oct 13 '22 15:10

Khawer Khaliq