I think the discussion about whether or not Comparable
should be synthesised is separate from whether Bool
should be comparable.
Let's imagine, for the sake of argument, that we're talking about a way to implement Comparable
explicitly. This works on nightly:
enum SortOrder {
case ascending
case descending
}
func isLessThan<Root, each Value: Comparable>(
_ leftRoot: Root,
_ rightRoot: Root,
comparing properties: repeat (KeyPath<Root, each Value>, SortOrder)
) -> Bool {
for (kp, sortMode) in repeat each properties {
let (left, right) = (leftRoot[keyPath: kp], rightRoot[keyPath: kp])
switch sortMode {
case .ascending:
if left < right { return true }
if left != right { return false /* left > right */ }
case .descending:
if left > right { return true }
if left != right { return false /* left < right */ }
}
}
return false /* left == right */
}
And you can use this to conveniently compose comparable properties:
struct Account: Comparable {
var name: String
var isActive: Bool
static func < (lhs: Self, rhs: Self) -> Bool {
isLessThan(
lhs, rhs,
comparing: (\.isActive, .descending), (\.name, .ascending)
)
}
}
let accounts = [
Account(name:"Albert", isActive: false),
Account(name:"Alice", isActive: true),
Account(name:"Becka", isActive: false),
Account(name:"Bob", isActive: true)
]
for a in accounts.sorted() {
print(a)
}
// Prints:
// Account(name: "Alice", isActive: true)
// Account(name: "Bob", isActive: true)
// Account(name: "Albert", isActive: false)
// Account(name: "Becka", isActive: false)
(Godbolt lets you execute this in the browser)
This is certainly much more convenient than writing the comparison function yourself. It's explicit about which properties are compared in which order, and can allow for customisation if our default way of ordering booleans doesn't suit your use-case.
But it does rely on booleans being comparable. Otherwise this gets a lot more awkward, for no really good reason.