Common Equatable Class on Swift

Common Equatable class on Swift

In lieu of existential types, you need to use a type eraser:

public struct AnyEquatable: Equatable {
public let value: Any
private let equals: (Any) -> Bool

public init<E: Equatable>(_ value: E) {
self.value = value
self.equals = { ($0 as? E) == value }
}

public static func == (lhs: AnyEquatable, rhs: AnyEquatable) -> Bool {
return lhs.equals(rhs.value) || rhs.equals(lhs.value)
}
}

example usage:

let items = [
AnyEquatable(1),
AnyEquatable(1.23),
AnyEquatable(true),
AnyEquatable("some string")
]

let desired = "some string"
let desiredAsAnyEquatable = AnyEquatable(desired)
let desiredDescription = String(reflecting: desired)

for item in items {
let itemDescription = String(reflecting: item.value)
let isEqualToDesired = item == desiredAsAnyEquatable
print("\(itemDescription) is \(isEqualToDesired ? "": "not ")equal to \(desiredDescription)")
}

Example output:

1 is not equal to "some string"

1.23 is not equal to "some string"

true is not equal to "some string"

"some string" is equal to "some string"

Conforming to Equatable vs defining func ==

You are basically asking why we have protocols at all.
We don't have to speak about operators, exactly the same is valid for any protocol with methods, e.g:

protocol Closable {
func close()
}

struct A {
func close() {}
}

struct B: Closable {
func close() {}
}

let a = A()
a.close() // valid even without protocol

let b = B()
b.close() // valid with protocol

Equatable just says that some type has == operator defined. Thanks to the protocol we can define methods that work on all types conforming to the protocol, for example:

func <T: Closable>doSomething(closable: T) {
...
closable.close()
}

Without the protocol, we would have to define such a method for every type (A and B) separately. Protocols provide common interface that allows us to constrain the definition of other types. Equatable is very important in that regard because it is used heavily in the standard library.

For example:

let personList1 = [OtherPerson(id: "123"), OtherPerson(id: "124")]
let personList2 = [OtherPerson(id: "123"), OtherPerson(id: "124")]

let listsAreEqual = (personList1 == personList2) // won't compile

Why the example won't compile? Because == for Arrays is defined only if Element conforms to Equatable. It is not enough for == operator to exist.

Another typical use case is Array.contains. Without Equatable, that method won't work.

Declaring the type as Equatable won't cost you anything but it will give you many benefits immediately.

Operation on an array of structs implementing Equatable

The problem is, as the error says, you cannot use protocols with Self or associated type requirements as actual types – as you'd lose the type information for what those requirements were. In this case, you'd lose the type information for the parameters of the == implementation – as Equatable says they must be the same type as the conforming type (i.e Self).

The solution is almost always to build a type eraser. In the case of expecting types to be equal if their id properties are equal, this can be as simple as just storing the id property and comparing it in the == implementation.

struct AnyVehicle : Equatable {

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

let id : String

init<T : Vehicle>(_ base: T) {
id = base.id
}
}

(Note that I renamed your ID property to id in order to conform with Swift naming convention)

However, a more general solution would be to store a function in the type eraser that can compare two arbitrary Vehicle conforming instances based on their == implementation, after type-casting to ensure they are the same type as the concrete type that the type eraser was created with.

struct AnyVehicle : Equatable {

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

// forward to both lhs's and rhs's _isEqual in order to determine equality.
// the reason that both must be called is to preserve symmetry for when a
// superclass is being compared with a subclass.
// if you know you're always working with value types, you can omit one of them.
return lhs._isEqual(rhs) || rhs._isEqual(lhs)
}

let base: Identifiable

private let _isEqual: (_ to: AnyVehicle) -> Bool

init<T : Vehicle>(_ base: T) {

self.base = base

_isEqual = {

// attempt to cast the passed instance to the concrete type that
// AnyVehicle was initialised with, returning the result of that
// type's == implementation, or false otherwise.
if let other = $0.base as? T {
return base == other
} else {
return false
}
}
}
}

print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Tractor(id: "foo"))) // false
print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Car(id: "bar"))) // false
print(AnyVehicle(Car(id: "foo")) == AnyVehicle(Car(id: "foo"))) // true

var array = [AnyVehicle]()

array.append(AnyVehicle(Car(id: "VW")))
array.append(AnyVehicle(Car(id: "Porsche")))
array.append(AnyVehicle(Tractor(id: "John Deere")))
array.append(AnyVehicle(Tractor(id: "Steyr")))

var op = Operator()

// compiles fine as AnyVehicle conforms to Equatable.
op.operationOnCollectionOfEquatables(array: array)

How to implement Equatable protocol for a protocol based on the identity of two instances that implement this protocol?

There are two problems here. The first is that you can't use ObjectIdentifier on value types. So you must declare this protocol to require reference (class) types:

protocol NetworkSubscriber : class, Equatable {
func onMessage(_ packet: NetworkPacket)
}

(Please do not add a lowercase i to the beginning of protocols. This is confusing in several ways in Swift.)

Then, you cannot use this protocol as a type. It describes a type (because it relies on Self via Equatable). So functions that accept it must be generic.

func ==<T: NetworkSubscriber>(lhs: T, rhs: T) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}

Given that NetworkSubscriber must be a class, you should ask very carefully whether you should be using inheritance here rather than a protocol. Protocols with associated types are very complex to use, and mixing classes and protocols creates even more complexity. Class inheritance is much simpler if you're already using classes.

Swift Equatable Generic type comparison inside generic function

In the general case, two Node objects aren't comparable. It depends on the kind of tree they're found in. It would make sense, for example, if nodes were only constrained to be valid members of a binary tree, but this isn't the case.

Luckily, you don't need Node to be Comparable, you can just need for its value to be Comparable:

class Node<T: Comparable> {
let value: T
let left: Node<T>?
let right: Node<T>?

init(value: T, left: Node<T>? = nil, right: Node<T>? = nil) {
self.value = value
self.left = left
self.right = right
}
}

extension Node: Equatable {
static func == (lhs: Node, rhs: Node) -> Bool {
return lhs.value == rhs.value
&& lhs.left == rhs.left
&& lhs.right == rhs.right
}
}

extension Node {
func isBinarySubTree() -> Bool {
return left.map { $0.value < self.value } ?? true
&& right.map { self.value < $0.value } ?? true
&& left?.isBinaryTree() ?? true
&& right?.isBinaryTree() ?? true
}
}

class AnyComparable inherits from class AnyEquatable

Note that your initialiser in the subclass does override the initialiser in the superclass. If you fix the constraint on C as the compiler asks you to, the next thing the compiler will tell you, is that you are missing an override keyword.

This behaviour is quite consistent, so it seems like it is deliberately designed that the generic constraints are not taken into account when determining whether one method overrides another. Here's another example of this:

class A {
func f<T>(t: T) {}
}

class B: A {
func f<C: Equatable>(t: C) {} // error
}

Anyway, one way you can work around this is to just change the argument label:

public init<C: Comparable>(comparable value: C) {
self.compares = { ($0 as? C).map { $0 < value } ?? false }
super.init(value)
}

The downside of this is that the client code gets a bit less concise. You have to do AnyComparable(comparable: something) every time, which is quite long. One solution is to put both AnyEquatable and AnyComparable in the same file, make both initialisers fileprivate. You can then provide factory functions (in the same file) that create these classes. For example:

public extension Equatable {
func toAnyEquatable() -> AnyEquatable {
.init(equatable: self)
}
}

public extension Comparable {
func toAnyComparable() -> AnyComparable {
.init(comparable: self)
}
}

Also note that your implementation of < doesn't make much sense. It should be:

public static func < (lhs: AnyComparable, rhs: AnyComparable) -> Bool {
lhs.compares(rhs.value) || (rhs != lhs && !rhs.compares(lhs.value))
}

and compares should mean "less than",

self.compares = { ($0 as? C).map { value < $0 } ?? false }

to say "lhs < rhs or rhs > lhs".

But even still, there are cases where using this class with completely unrelated types still doesn't make much sense, just FYI:

let a = AnyComparable(comparable: "foo")
let b = AnyComparable(comparable: 1)
print(a < b) // true
print(a > b) // true
print(a <= b) // false
print(a >= b) // false
print(a == b) // false

So please do be careful!

Swift 2 - Protocol conforming to Equatable issue

So I found a solution to get around the Equatable requirement by extending CollectionType to define a new indexOf for elements are of Peer type, which takes advantage of the other closure-based indexOf. This is essentially a convenience function which saves me from using the closure indexOf directly. Code below:

extension CollectionType where Generator.Element == Peer {
func indexOf(element: Generator.Element) -> Index? {
return indexOf({ $0.name == element.name })
}
}

This of course assumes everything I need to test equality can be obtained from the Peer protocol (which is true for my specific use case).

EDIT: Update for Swift 3:

extension Collection where Iterator.Element == Peer {
func indexOf(element: Iterator.Element) -> Index? {
return index(where: { $0.name == element.name })
}
}


Related Topics



Leave a reply



Submit