Iâm bumping this thread because I just looked through one of my projects and found that, although it has only a small number of sort
calls, every single one of them would benefit from this proposal.
My feedback on the proposal is that I strongly prefer to have the comparison predicate first and the transform second. That is, the call site should look like this:
items.sorted(by: >){ $0.foo(bar) }
The comparison will almost always be either â<â or â>â, whereas the transform will often be a trailing closure, and putting them in this order enables that.
Having the default of â<â when the transformed values are Comparable
is a basic and obvious thing to include. Sorting is frequently done in increasing order, and Swiftâs existing sort functions all use â<â as a default, so these new ones should match that.
The spelling looks great as well:
items.sorted{ $0.foo(bar) }
⢠⢠â˘
If it werenât for the fact that tuples cannot conform to protocols, I would almost say that the transform should be *required* to return a Comparable
result. After all, it is mapping elements for the express purpose of sorting.
I mention tuples because, with this proposal, it will be quite easy to sort on multiple keys, provided they go in the same order:
items.sorted(by: <){ ($0.size, $0.color, $0.price) }
For numeric keys, the individual fields can have their sort orders independently reversed by unary negation, but that does not work for strings or enums and so forth. Perhaps a future pitch will introduce a better way of sorting on multiple keys. Something as simple as an order-reversing wrapper would work:
Order-reversing wrapper
struct Reversed<T: Comparable>: Comparable {
var value: T
init(_ value: T) { self.value = value }
static func < (lhs: Self, rhs: Self) -> Bool {
return rhs.value < lhs.value
}
}
Of course, sorting on multiple keys via tuples is just a neat thing that can be done, it is not the driving impetus here.
⢠⢠â˘
Regarding the argument labels, I agree that âbyâ is correct for the comparison predicate, matching Swiftâs existing sort functions. For the transform, there are several acceptable options, such as âonâ, âviaâ, and âusingâ. These are grammatical as shown:
âSort by a predicate, on a keyâ
âSort by a predicate, via a mapâ
âSort by a predicate, using a transformâ
The word âviaâ might be too obscure, âonâ is nice and short, and âusingâ reads well but takes up a lot of space. In practice, with the argument order allowing the transform to be a trailing closure, this label will rarely be seen.
The existing map
function has no label for its transform argument, so that might be worth considering. It reads like âbyâ applies to both arguments, which in a way it does. We are sorting by a predicate, and also by a key. When using a keypath, we thus have:
items.sorted(by: >, \.name)
Then if the default ordering is used, the call site is extremely tidy:
items.sorted(\.name)
Now that I see it written out, I actually like that a lot.
⢠⢠â˘
Iâm not sure how to feel about the isExpensiveTransform
flag. I expect the transforms will be quite cheap in the vast majority of cases, so I donât know if itâs worth complicating the API surface. I donât have a strong opinion either way on this right now.