Skip to content

Instantly share code, notes, and snippets.

@Sajjon
Last active October 11, 2019 15:57
Show Gist options
  • Select an option

  • Save Sajjon/3f7dafdd341a42e675f3d830572c50b2 to your computer and use it in GitHub Desktop.

Select an option

Save Sajjon/3f7dafdd341a42e675f3d830572c50b2 to your computer and use it in GitHub Desktop.
Auto Synthesizer for Equatable
// MARK: - Compare + Mirror
private func compareSome<T>(lhs: T, rhs: T, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: Bool) -> Bool {
compareAny(lhs: lhs, rhs: rhs, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer)
}
private func compareAny(lhs: Any, rhs: Any, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: Bool = true) -> Bool {
let lMirror = Mirror(reflecting: lhs)
let rMirror = Mirror(reflecting: rhs)
guard
lMirror.displayStyle == rMirror.displayStyle,
lMirror.children.count == rMirror.children.count
else
{ return false }
for indexInt in 0..<lMirror.children.count {
let index = AnyCollection<(label: Optional<String>, value: Any)>.Index(indexInt)
let lChild = lMirror.children[index]
let rChild = rMirror.children[index]
guard lChild.label == rChild.label else {
return false
}
guard "\(lChild.value)" == "\(rChild.value)" else {
let lChildType = type(of: lChild.value)
let rChildType = type(of: rChild.value)
if beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer && "\(lChildType)" == "\(rChildType)" {
continue
} else {
return false
}
}
}
return true
}
// MARK: - AutoEquatable
public protocol AutoEquatable: Equatable {
static func compare(lhs: Self, rhs: Self, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: Bool) -> Bool
}
public extension AutoEquatable {
static func compare(lhs: Self, rhs: Self, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: Bool) -> Bool {
compareSome(lhs: lhs, rhs: rhs, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer)
}
}
// MARK: - Equatable
public extension AutoEquatable {
static func == (lhs: Self, rhs: Self) -> Bool {
Self.compare(lhs: lhs, rhs: rhs, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: true)
}
}
// MARK: - ErrorAutoEquatable
public typealias ErrorAutoEquatable = Swift.Error & AutoEquatable
public func == <LHS>(lhs: LHS, rhsAnyError: Swift.Error) -> Bool where LHS: ErrorAutoEquatable {
guard let rhs = rhsAnyError as? LHS else { return false }
return compareSome(lhs: lhs, rhs: rhs, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: true)
}
public func == <RHS>(lhsAnyError: Swift.Error, rhs: RHS) -> Bool where RHS: ErrorAutoEquatable {
guard let lhs = lhsAnyError as? RHS else { return false }
return compareSome(lhs: lhs, rhs: rhs, beSatisfiedWithSameAssociatedTypeIfTheirValuesDiffer: true)
}
// MARK: - Example
// MARK: Example Error types
enum AnError: ErrorAutoEquatable {
case anInt(Int)
case aString(String?)
case anotherInt(Int)
case anotherString(String?)
case aBool(Bool?)
case stringAndInt(aString: String, anInt: Int)
case complex(aString: String, anInt: Int, aBool: Bool, aTuple: (String, Int, Bool))
}
enum AnotherError: ErrorAutoEquatable {
case anInt(Int)
}
// MARK: - Usage
func example() {
@discardableResult
func isSame(_ lhs: AnError, _ rhs: AnError) -> Bool {
return lhs == rhs
}
// ALL `true`
isSame(.anInt(1), .anInt(2)) // true
isSame(.aBool(true), .aBool(false)) // true
isSame(.aString("Hello"), .aString("World")) // true
isSame(.aString("Hello"), .aString(nil)) // true
isSame(
.complex(aString: "A", anInt: 1, aBool: true, aTuple: ("B", 2, false)),
.complex(aString: "X", anInt: 8, aBool: false, aTuple: ("Y", 9, true))
) // true
(AnError.anInt(1) as Swift.Error) == AnError.anInt(1) // true
AnError.anInt(1) == (AnError.anInt(1) as Swift.Error) // true
// ALL `false`
isSame(.anInt(1), .anotherInt(1)) // false
isSame(.aString("Hello"), .anotherString("World")) // false
isSame(.aString(nil), .anotherString(nil)) // false
(AnError.anInt(1) as Swift.Error) == AnotherError.anInt(1) // false
AnError.anInt(1) == (AnotherError.anInt(1) as Swift.Error) // false
}
// MARK: Advanced - 'Static' (using Functions)
protocol EmptyInitializable {
init()
}
protocol ValueIgnored {
static var irrelevant: Self { get }
}
typealias Ignored = EmptyInitializable & ValueIgnored
extension ValueIgnored where Self: EmptyInitializable {
static var irrelevant: Self { .init() }
}
func == <E, A>(instance: E, make: (A) -> E) -> Bool where E: ErrorAutoEquatable, A: Ignored {
instance == make(A.irrelevant)
}
func != <E, A>(instance: E, make: (A) -> E) -> Bool where E: ErrorAutoEquatable, A: Ignored {
!(instance == make)
}
func == <E, A, B>(instance: E, make: (A, B) -> E) -> Bool where E: ErrorAutoEquatable, A: Ignored, B: Ignored {
instance == make(A.irrelevant, B.irrelevant)
}
extension String: Ignored {}
extension Bool: Ignored {}
extension UInt: Ignored {}
extension UInt64: Ignored {}
extension UInt32: Ignored {}
extension UInt16: Ignored {}
extension UInt8: Ignored {}
extension Int: Ignored {}
extension Int64: Ignored {}
extension Int32: Ignored {}
extension Int16: Ignored {}
extension Int8: Ignored {}
// MARK: Advanced example
func advanced() {
AnError.anInt(1) == AnError.self.anInt // true
AnError.stringAndInt(aString: "Some string", anInt: 123) != AnError.self.anInt // true, not equal
AnError.stringAndInt(aString: "Some string", anInt: 123) == AnError.stringAndInt // true
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment