# Adding a polymorphic Equatable?

As Thomas correctly points out the code I posted was incorrect. Corrected version below:

import Foundation

protocol AnyEquatable {
func equals(rhs: AnyEquatable) -> Bool
func canEqualReverseDispatch(lhs: AnyEquatable) -> Bool
}

/*final*/ func ==(lhs: AnyEquatable, rhs: AnyEquatable) -> Bool {
return lhs.equals(rhs: rhs) // Fix the type of the LHS using dynamic dispatch.
}
/*final*/ func !=(lhs: AnyEquatable, rhs: AnyEquatable) -> Bool {
return !lhs.equals(rhs: rhs) // Fix the type of the LHS using dynamic dispatch.
}

class Point2D: AnyEquatable {
let x: Double
let y: Double
init(x: Double, y: Double) {
self.x = x
self.y = y
}
func equals(rhs: Point2D) -> Bool {
return x == rhs.x && y == rhs.y
}
func equals(rhs: AnyEquatable) -> Bool {
guard rhs.canEqualReverseDispatch(lhs: self), let r = rhs as? Point2D else { // Fix type of RHS via a failable cast.
return false // or fatalError("Coding Error: unequatable types; lhs: \(self), rhs: \(rhs).")
}
return equals(rhs: r) // LHS and RHS both Point2Ds.
}
func canEqualReverseDispatch(lhs: AnyEquatable) -> Bool {
return lhs is Point2D // By default derrived types may be equal.
}
}

let p20 = Point2D(x: 0, y: 0)
let p21 = Point2D(x: 1, y: 1)
p20 == p20 // T
p20 == p21 // F

// PointPolar can be added retrospectively (main point of technique!) and can be equal to a Point2D.
class PointPolar: Point2D {
init(rho: Double, theta: Double) {
super.init(x: rho * cos(theta), y: rho * sin(theta))
}
}

let pp0 = PointPolar(rho: 0, theta: 0)
pp0 == p20 // T
p20 == pp0 // T
pp0 == p21 // F
p21 == pp0 // F

// Point3D can be added retrospectively (main point of technique!), but must be always unequal to a Point2D.
class Point3D: Point2D {
let z: Double
init(x: Double, y: Double, z: Double) {
self.z = z
super.init(x: x, y: y)
}
func equals(rhs: Point3D) -> Bool {
return x == rhs.x && y == rhs.y && z == rhs.z
}
override func equals(rhs: AnyEquatable) -> Bool {
guard rhs.canEqualReverseDispatch(lhs: self), let r = rhs as? Point3D else { // Fix type of RHS via a failable cast.
return false // or fatalError("Coding Error: unequatable types; lhs: \(self), rhs: \(rhs).")
}
return equals(rhs: r) // LHS and RHS both Point3Ds.
}
override func canEqualReverseDispatch(lhs: AnyEquatable) -> Bool {
return lhs is Point3D // Make Point3D unequal to Point2D.
}
}

let p30 = Point3D(x: 0, y: 0, z: 0)
let p31 = Point3D(x: 1, y: 1, z: 1)
p30 == p30 // T
p30 == p31 // F

p20 == p30 // F
p30 == p20 // F
p21 == p30 // F
p30 == p21 // F
p20 == p31 // F
p31 == p20 // F
p21 == p31 // F
p31 == p21 // F

var result = ""
let ps: [AnyEquatable] = [p20, p21, pp0, p30, p31]
for po in ps {
for pi in ps {
result += ", \(po == pi)"
}
}
result // TFTFF FTFFF TFTFF FFFTF FFFFT

Have tested the code better and also cross checked against “Programming in Scala”. That will teach me to post in haste.

1 Like