I'm fairly skeptical of introducing a new SortDescriptor type for this purpose. The beauty of sort taking a closure is that closures are so flexible. They can cover any comparison – less than, greater than, caseless (localized or not), stringly or numerically, partial comparisons like ignoring prefixes. All by taking a single argument, a function from two elements to bool.
It would be nice to stick with this flexibility, and one way to do that is just to take a sequence of predicates:
extension MutableCollection where Self: RandomAccessCollection {
// an actual proposal should be generic over types other than just an Array of comparators
mutating func sort<Comparisons: Sequence>(by comparators: Comparisons)
where Comparisons.Element == (Element,Element) -> Bool
}
The usage:
people.sort(by: [
{ $0.age < $1.age },
{ $0.name < $1.name },
])
doesn't seem significantly different to the proposed:
people.sorted(by: [
SortDescriptor({ $0.age }),
SortDescriptor({ $0.name })
])
In fact, it looks a bit more lightweight to me. But without the need for a new top-level type, and without giving up as much flexibility.
If you're keen to add more sugar, this can still be done without more types, with the addition of functions that transform a key path into a predicate:
func lesser<T,U: Comparable>(of path: KeyPath<T,U>) -> (T,T)->Bool {
return { $0[keyPath: path] < $1[keyPath: path] }
}
func greater<T,U: Comparable>(of path: KeyPath<T,U>) -> (T,T)->Bool {
return { $0[keyPath: path] > $1[keyPath: path] }
}
people.sort(by: [lesser(of: \.age), greater(of: \.name)])