Equals is a Swift µframework to reduce boilerplate code when conforming to Equatable and Hashable protocols.
- Swift 3.0
- Any Swift 3.0 compatible platform (iOS 8.0+ / Mac OS X 10.10+ / tvOS 9.0+ / watchOS 2.0+ / Linux)
Add Equals to your pod file and run pod install:
pod 'Equals', '~> 2.0.0'Add Equals to your Cartfile (package dependency) or Cartfile.private (development dependency):
github "tomquist/Equals" ~> 2.0.0
Add to your Package.swift dependencies:
import PackageDescription
let package = Package(
// ... your project details
dependencies: [
// As a required dependency
.Package(url: "ssh://[email protected]/tomquist/Equals.git", majorVersion: 2)
],
testDependencies: [
// As a test dependency
.Package(url: "ssh://[email protected]/tomquist/Equals.git", majorVersion: 2)
]
)Lets start with a simple struct:
struct Person {
let firstName: String?
let lastName: String
let children: [Person]
}To conform this type to Equatable we first have to declare conformance, e.g. by providing an empty extension and provide an operator overload for ==:
extension Person: Equatable {}
func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.firstName == rhs.firstName
&& lhs.lastName == rhs.lastName
&& lhs.children == rhs.children
}As you can see, this is a lot of code for such a simple type and each property is listed twice.
Even worse, when conforming to Hashable you additionally have to provide a complex hashValue property:
extension Person: Hashable {
var hashValue: Int {
var result = 17
result = 31 &* result &+ (firstName?.hashValue ?? 0)
result = 31 &* result &+ lastName.hashValue
for child in children {
result = 31 &* result &+ child.hashValue
}
return result
}
}Using Equals, this is a lot easier. To conform to Hashable, all you have to do is to conform to the EqualsHashable protocol:
extension Person: EqualsHashable {
static let hashes: Hashes<Person> = Hashes()
.append {$0.firstName}
.append {$0.lastName}
.append {$0.children}
}If you don't need hashing capabilities, just conform to the EqualsEquatable protocol:
extension Person: EqualsEquatable {
static let equals: Equals<Person> = Equals()
.append {$0.firstName}
.append {$0.lastName}
.append {$0.children}
}That's it! Now you can compare your type, using the == operator, put it into Sets or use it as a Dictionary key.
Equals currently provides append functions for four types of properties:
Equatable/HashablepropertiesOptionalproperties of typeEquatable/HashableCollectionTypeproperties where the elements areEquatable/Hashable- Any other property, but you have to provide equals and hashValue functions.
Sometimes, you explicitly have to specify which append method to use. E.g. lets change the property children of our Person example above into type Set<Person>. Because Set already conforms to Hashable, we now get a compiler error:
Ambiguous use of 'append(hashable:)'
This is because there are potentially several append methods that could be used in this situation. To avoid this error, we can change our implementation of EqualsHashable into this:
extension Person: EqualsHashable {
static let hashes: Hashes<Person> = Hashes()
.append {$0.firstName}
.append {$0.lastName}
.append(hashable: {$0.children})
}