Currently Comparable
inherits from Equatable
, but does not provide a default implementation for ==
, so the compiler tries to synthesize one composed of member-wise ==
s. This leads to a problem where if a type's <
is not composed of member-wise inequalities, then <
, >
, and ==
all evaluate to false
for some pairs of values. For example:
struct Length {
enum Unit: Double, Comparable {
case mm = 0.001
case m = 1
case km = 1000
case banana = 0.178 // according to bananaforscale.info
}
let magnitude: Double
let unit: Unit
static func < (lhs: Self, rhs: Self) -> Bool {
lhs.magnitude * lhs.unit.rawValue < rhs.magnitude * rhs.unit.rawValue
}
}
let aBanana = Length(magnitude: 1, unit: .banana)
let anotherBanana = Length(magnitude: 0.178, unit: .m)
print(aBanana < anotherBanana) // prints 'false'
print(aBanana > anotherBanana) // prints 'false'
print(aBanana == anotherBanana) // prints 'false'
Logically, a = b iff a ≮ b and a ≯ b, so one would reasonably think that aBanana == anotherBanana
, because neither the <
nor the >
evaluates to true
. This is also the source of a SwiftPM bug where one package version can be neither equal to nor greater/lesser than another at the same time.
Additionally, the current behaviour does not match Comparable
's documentation:
Types with Comparable conformance implement the less-than operator (
<
) and the equal-to operator (==
). These two operations impose a strict total order on the values of a type, in which exactly one of the following must be true for any two valuesa
andb
:
a == b
a < b
b < a
I tried to think of an example where the status quo is preferred, but can't come up with a good one.
Adding a default implementation of ==
to Comparable
like this:
extension Comparable {
@inlinable
static func == (lhs: Self, rhs: Self) {
!(lhs < rhs || lhs > rhs)
}
}
is silently source breaking, but likely the sources it breaks are already incorrect.
What does everyone think?