Not sure when you should make your Swift types Equatable
? What about Comparable
? In this post I look at two common situations where using the Swift Standard Library gets easier when you do.
Update 2 April 2018: Starting with Swift 4.1 you can have the compiler automatically synthesize Equatable
conformance for your type. See How To Get Equatable And Hashable For Free.
A Simple Example
Let’s use a simple structure for a country that expects a name and capital and has a flag indicating if we have visited:
struct Country {
let name: String
let capital: String
var visited: Bool
}
We can use the default initializer to create some examples:
let canada = Country(name: "Canada", capital: "Ottawa", visited: true)
let australia = Country(name: "Australia", capital: "Canberra", visited: false)
We can also already store these in an array (but not yet a Set or Dictionary - that needs hashing which is for another time):
let bucketList = [brazil,australia,canada,egypt,uk,france]
All Things Being Equal
What if we want to check if a specific country is in our bucket list array? Well the long way is to use the contains(where:)
instance method of Array
. This expects a predicate that returns a boolean indicating if an element of the array matches. So to test if our bucket list contains a specific country we could write:
let object = canada
let containsObject = bucketList.contains { (country) -> Bool in
return country.name == object.name &&
country.capital == object.capital &&
country.visited == object.visited
}
This is a pain and as you probably already know there is an easier way. The Swift standard library has an Equatable
protocol that we can adopt by adding the static ==
operator function to our type in an extension:
extension Country: Equatable {
static func == (lhs: Country, rhs: Country) -> Bool {
return lhs.name == rhs.name &&
lhs.capital == rhs.capital &&
lhs.visited == rhs.visited
}
}
The ==
operator returns true
when the two Country
arguments are equal. Two Country
values are equal if all visible properties of the values are equal.
let visited = Country(name: "Australia", capital: "Canberra", visited: true)
let unvisited = Country(name: "Australia", capital: "Canberra", visited: false)
visited == australia // false
unvisited == australia // true
When your type is Equatable it can make use of the simpler contains
method of Array
that takes the value we are testing for. No predicate or closure needed:
bucketList.contains(canada) // true
Applying Order to the World
What if we want to sort our bucket list? To sort something we need to be able to compare two values. As with the Equatable
example we can sort using a predicate closure (sorting just on name for brevity):
bucketList.sorted(by: { $0.name < $1.name } )
That works but using the standard library to sort an array is easier if our type is also Comparable
. For that we need to add the static <
operator function as well as the ==
operator we already have for Equatable
. We can add both in a single extension (replacing the extension for Equatable
):
extension Country: Comparable {
static func == (lhs: Country, rhs: Country) -> Bool {
return lhs.name == rhs.name &&
lhs.capital == rhs.capital &&
lhs.visited == rhs.visited
}
static func < (lhs: Country, rhs: Country) -> Bool {
return lhs.name < rhs.name ||
(lhs.name == rhs.name && lhs.capital < rhs.capital) ||
(lhs.name == rhs.name && lhs.capital == rhs.capital && rhs.visited)
}
}
What should the natural order for our Country type be? For the purposes of this post I sort alphabetically based on the name first and then the capital and finally unvisited before visited.
Now that our Country
type is comparable we can again drop the closure with a predicate for the simpler form of sorted
bucketList.sorted()
All Done
That’s all there is to it. I used a value type (Swift struct) but the code is similar if we are working with a class. For me at least I find it easier to grasp the concept behind somewhat abstract protocols when I think in terms of what supporting the protocol allows me to do. In this case I added Equatable
to simplify testing if my object was part of a collection and Comparable
to more easily sort that collection.
Further Reading
From the Swift Standard Library