That's a great suggestion that I hadn't thought of before! I completely prefer that design over the design I proposed initially. It fits in much better with the existing standard library, and it avoids having to add a new type.
My dream syntax would be:
people.sorted(by: [
{ $0.name },
{ $0.age }
])
but we can't express that in the type system without variadic generics. The main reason I've been using a SortDescriptor
type is to type-erase the map
closures.
I'm still very keen on the map
-closure design, though. For symmetry, that could potentially give us four designs:
// (1) The current standard library, sort(by: (Element, Element) -> Bool)
people.sort(by: { $0.age < $1.age })
// (2) A map-closure design, sort<T: Comparable>(by: (Element) -> T)
people.sort(by: { $0.age })
// (3) An improved "Sort Descriptor" design: sort(by: [(Element, Element) -> Bool])
people.sort(by: [
{ $0.age < $1.age },
{ $0.name < $1.name }
])
// (4) An additional design for symmetry, that would require Variadic Generics.
// Not currently possible.
people.sort(by: [
{ $0.age },
{ $0.name }
])
So that being said, there are a few open questions:
-
Is it acceptable to have (2) and (3) but not (4)? That would feel a bit asymetric to me.
-
If this was packaged together as a comprehensive "sort ergonomics" proposal, would it make sense to also conditionally propose (4) (the design that requires variadic generics) on the precondition that it would be implemented if/when it became possible?
-
Is it even desirable to have four overloads of
sort(by:)
? If not, which one do we cut? I think (3) is filling a bigger gap / solving a more important problem, while (2) leans closer towards "sugar". That's probably a strong indicator, but I would also be sad to miss out on (2) if we cut it out.
Of all of those options, I think the most sensible choice may potentially be to slim the proposal to just include sort(by: [(Element, Element) -> Bool])
.