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
How to Block Users on Firebase in a Social Media App? for iOS
How to Use Dispatch Groups to Wait to Call Multiple Functions That Depend on Different Data
Convert Float Value to String in Swift
Swift "Where" Array Extensions
How to Append a Character to a String in Swift
Swift: How to Change a Property's Value Without Calling Its Didset Function
How to Make Rounded Corner Progress Bar in Swift
Pass Optional Block or Closure to a Function in Swift
Firebase Sign Out Not Working in Swift
How to Represent Magnitude for Mass in Swift
App Crashes When Trying to Append Data to a Child Value
Scene Created in Sprite Kit Level Editor Is Not Working
How to Change Gb-2312 Encoding to Utf-8
How to Encode Realm's List<> Type