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