Reading Apple's site's notes on Comparable
states that a type may have states that don't compare to anything. A comparison operator should return false
if at least one operand is in the incomparable class. This violates the expected not-conditions the various operators have with each other (==
opposes !=
, <
opposes >=
, >
flips <
and opposes <=
); this philosophy lies to the user.
If only there was some way for a type to Optional
-ly declare comparisons. Hmm....
protocol PartiallyEquatable {
static func ==?(_ lhs: Self, _ rhs: Self) -> Bool?
}
extension PartiallyEquatable {
static func !==?(_ lhs: Self, _ rhs: Self) -> Bool? {
guard let result = lhs ==? rhs else { return nil }
return !result
}
}
protocol PartiallyComparable: PartiallyEquatable {
static func <?(_ lhs: Self, _ rhs: Self) -> Bool?
}
extension PartiallyComparable {
static func >?(_ lhs: Self, _ rhs: Self) -> Bool? {
return rhs <? lhs
}
static func <=?(_ lhs: Self, _ rhs: Self) -> Bool? {
guard let result = lhs >? rhs else { return nil }
return !result
}
static func >=?(_ lhs: Self, _ rhs: Self) -> Bool? {
guard let result = lhs <? rhs else { return nil }
return !result
}
}
The next problem is that the Partial and regular comparison protocols are related, but which way should the implementations go. There are arguments for each direction.
For integer types, the partial comparison operators should trivially forward to the regular versions:
protocol ActuallyEquatable: Equatable, PartiallyEquatable {}
extension ActuallyEquatable {
static func ==?(_ lhs: Self, _ rhs: Self) -> Bool? { return lhs == rhs }
}
protocol ActuallyComparable: Comparable, PartiallyComparable, ActuallyEquatable {}
extension ActuallyComparable {
static func <?(_ lhs: Self, _ rhs: Self) -> Bool? { return lhs < rhs }
}
protocol BinaryInteger: ActuallyComparable { /*...*/ }
(I don't know if Swift allows this kind of partial specialization.)
For floating-point types, we flip the dependencies to maintain the lie IEEE-754 demands:
protocol FakingEquatability: PartiallyEquatable, Equatable {}
extension FakingEquatability {
static func ==(_ lhs: Self, _ rhs: Self) -> Bool { return (lhs ==? rhs) ?? false }
static func !=(_ lhs: Self, _ rhs: Self) -> Bool { return (lhs !=? rhs) ?? false }
}
protocol FakingComparability: PartiallyComparable, Comparable, FakingEquatability {}
extension FakingComparability {
static func <(_ lhs: Self, _ rhs: Self) -> Bool { return (lhs <? rhs) ?? false }
static func >(_ lhs: Self, _ rhs: Self) -> Bool { return (lhs >? rhs) ?? false }
static func <=(_ lhs: Self, _ rhs: Self) -> Bool { return (lhs <=? rhs) ?? false }
static func >=(_ lhs: Self, _ rhs: Self) -> Bool { return (lhs >=? rhs) ?? false }
}
protocol FloatingPoint: FakingComparability { /*...*/ }