[Discussion] Sortable Attribute


(Charlie Monroe) #1

Hi there,

I'm not really sure whether this is appropriate, considering that Swift 4 should be basically focusing purely on ABI compatibility and a few other issues mentioned by Chris, but I've been thinking about this for some time and would make a lot of lives easier, so it could be part of 3.x.

I've written a proposal draft here: https://gist.github.com/charlieMonroe/0752cd61f7937f714b689137daf9de21

In brevity:

Currently, if you want consistent sorting of some entities, you either declare them Comparable and decide under which key do they sort and then use entities.sorted().

Unfortunately, this isn't really clear under what key do the entities sort. For example:

class Person {
    var age: Int = 21
    var firstName: String = "John"
    var lastName: String = "Doe"
}

If this were Comparable, would it sort by first name, last name or age? What if you wanted to be able to sort using all the properties? You'd probably resort to writing an extension on sequence:

extension Sequence where Self.Iterator.Element: MyClass {
    func sortedByFirstName() -> [Self.Iterator.Element] {
        return self.sorted(isOrderedBefore: { $0.firstName < $1.firstName })
    }
    func sortedByLastName() -> [Self.Iterator.Element] {
        return self.sorted(isOrderedBefore: { $0.lastName < $1.lastName })
    }
    func sortedByAge() -> [Self.Iterator.Element] {
        return self.sorted(isOrderedBefore: { $0.age < $1.age })
    }
}

This is fairly tedious, however. What I propose is to add a @sortable attribute to properties, which would automatically generate the code for you. It - of course - requires the property to conform to Comparable...

Any thoughts on this?


(Xiaodi Wu) #2

A core team-driven proposal that didn't get sufficiently refined before the
Swift 3 cutoff was to refine Equatable and Comparable. The direction that
the core team was going was this: if your class is Comparable, then it
defines a total order, and `sort()` and `sorted()` are to be a stable sort
based on that total order.

Now, as to sorting based on particular properties, Dave has implemented the
renaming of predicate labels so it's not so onerous anymore to write
`self.sorted { $0.age < $1.age }` or even `self.sorted(by: { $0.age <
$1.age })`. Why are you generating your own wrapper functions for these at
all? Once refinements to Equatable and Comparable go in, it's easy to see
how one might have a syntax where you might have `self.sorted(.ascending,
by: { $0.age })`. So I guess, I don't know that this sugar is gaining too
much because I don't see the motivation for generating these wrapper
methods at all...

···

On Tue, Aug 16, 2016 at 3:51 PM, Charlie Monroe via swift-evolution < swift-evolution@swift.org> wrote:

Hi there,

I'm not really sure whether this is appropriate, considering that Swift 4
should be basically focusing purely on ABI compatibility and a few other
issues mentioned by Chris, but I've been thinking about this for some time
and would make a lot of lives easier, so it could be part of 3.x.

I've written a proposal draft here: https://gist.github.com/charlieMonroe/
0752cd61f7937f714b689137daf9de21

In brevity:

Currently, if you want consistent sorting of some entities, you either
declare them Comparable and decide under which key do they sort and then
use entities.sorted().

Unfortunately, this isn't really clear under what key do the entities
sort. For example:

class Person {
    var age: Int = 21
    var firstName: String = "John"
    var lastName: String = "Doe"
}

If this were Comparable, would it sort by first name, last name or age?
What if you wanted to be able to sort using all the properties? You'd
probably resort to writing an extension on sequence:

extension Sequence where Self.Iterator.Element: MyClass {
    func sortedByFirstName() -> [Self.Iterator.Element] {
        return self.sorted(isOrderedBefore: { $0.firstName < $1.firstName
})
    }
    func sortedByLastName() -> [Self.Iterator.Element] {
        return self.sorted(isOrderedBefore: { $0.lastName < $1.lastName })
    }
    func sortedByAge() -> [Self.Iterator.Element] {
        return self.sorted(isOrderedBefore: { $0.age < $1.age })
    }
}

This is fairly tedious, however. What I propose is to add a @sortable
attribute to properties, which would automatically generate the code for
you. It - of course - requires the property to conform to Comparable...

Any thoughts on this?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Anton Zhilin) #3

What we need here is sort_by from Ruby. How about adding these overloads?

func sort<U: Comparable>(by: (T) -> U)
func sorted<U: Comparable>(by: (T) -> U) -> [T]


(Xiaodi Wu) #4

I'd be in favor. We'd need another name, since the current closure
predicate is already standardized to `by:`.
(Or, as I propose above, once `Ordering` comes to the stdlib, we can just
have it as `func sort<U: Comparable>(_ ordering: Ordering, by: (T) -> U)`,
called like `sort(.ascending) { $0.age }`.)

···

On Tue, Aug 16, 2016 at 5:35 PM, Anton Zhilin <antonyzhilin@gmail.com> wrote:

What we need here is sort_by from Ruby. How about adding these overloads?

func sort<U: Comparable>(by: (T) -> U)
func sorted<U: Comparable>(by: (T) -> U) -> [T]


(Anton Zhilin) #5

Agreed, because we wouldn't be able to perform a descending sort otherwise.

···

2016-08-17 1:45 GMT+03:00 Xiaodi Wu <xiaodi.wu@gmail.com>:

On Tue, Aug 16, 2016 at 5:35 PM, Anton Zhilin <antonyzhilin@gmail.com> > wrote:

What we need here is sort_by from Ruby. How about adding these
overloads?

func sort<U: Comparable>(by: (T) -> U)
func sorted<U: Comparable>(by: (T) -> U) -> [T]

I'd be in favor. We'd need another name, since the current closure
predicate is already standardized to `by:`.
(Or, as I propose above, once `Ordering` comes to the stdlib, we can just
have it as `func sort<U: Comparable>(_ ordering: Ordering, by: (T) -> U)`,
called like `sort(.ascending) { $0.age }`.)


(Silvan Mosberger) #6

I'd be in favor. We'd need another name, since the current closure predicate is already standardized to `by:`.

Haskell uses "on" for sorting with a mapping. There are both

sortOn :: Ord b => (a -> b) -> [a] -> [a]

and

sortBy :: (a -> a -> Ordering) -> [a] -> [a]

in Haskell.


(Charlie Monroe) #7

My original intention was to get rid of re-defining what should be the sequence sorted by. For example, if self.sorted { $0.age < $1.age } was at 10 places in code, I would not consider it a good coding technique and would definitely suggest the person to create a method for this - sortedByAge().

self.sorted(.ascending) { $0.age } is a bit better, but I still feel that there is a lot of duplicate code and if one used this in 10 places over the project, a method for this should be created instead.

Don't you agree that people.sortedByAge() reads better than self.sorted(.ascending) { $0.age }?

What I was also aiming at is possibly something that could replace e.g. NSSortDescriptor, which can be handy in the UI, but requires dynamic (@objc) properties in order to work.

What I'm suggesting, would also emit a function symbol for sorting the array by a particular key, so the sorting could actually be dynamic - supply a key (using #keyPath()) which is @sortable, given name mangling is stable in Swift 4, you can create the well-defined function symbol name, make a lookup via dyld and call it.

Note that the NSSortDescriptor is just an example and given most of my development is on macOS, I use bindings and NSArrayController where the sort descriptors are fairly common unlike with iOS... But the usage of this can be extended to many other areas given that you *know* that there is a function out there, with that particular signature that will sort the array...

···

On Aug 17, 2016, at 12:45 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Aug 16, 2016 at 5:35 PM, Anton Zhilin <antonyzhilin@gmail.com <mailto:antonyzhilin@gmail.com>> wrote:
What we need here is sort_by from Ruby. How about adding these overloads?

func sort<U: Comparable>(by: (T) -> U)
func sorted<U: Comparable>(by: (T) -> U) -> [T]

I'd be in favor. We'd need another name, since the current closure predicate is already standardized to `by:`.
(Or, as I propose above, once `Ordering` comes to the stdlib, we can just have it as `func sort<U: Comparable>(_ ordering: Ordering, by: (T) -> U)`, called like `sort(.ascending) { $0.age }`.)


(Xiaodi Wu) #8

My original intention was to get rid of re-defining what should be the
sequence sorted by. For example, if self.sorted { $0.age < $1.age } was at
10 places in code, I would not consider it a good coding technique and
would definitely suggest the person to create a method for this -
sortedByAge().

self.sorted(.ascending) { $0.age } is a bit better, but I still feel that
there is a lot of duplicate code and if one used this in 10 places over the
project, a method for this should be created instead.

Don't you agree that people.sortedByAge() reads better than
self.sorted(.ascending) { $0.age }?

What I was also aiming at is possibly something that could replace e.g.
NSSortDescriptor, which can be handy in the UI, but requires dynamic
(@objc) properties in order to work.

What I'm suggesting, would also emit a function symbol for sorting the
array by a particular key, so the sorting could actually be dynamic -
supply a key (using #keyPath()) which is @sortable, given name mangling is
stable in Swift 4, you can create the well-defined function symbol name,
make a lookup via dyld and call it.

Note that the NSSortDescriptor is just an example and given most of my
development is on macOS, I use bindings and NSArrayController where the
sort descriptors are fairly common unlike with iOS... But the usage of this
can be extended to many other areas given that you *know* that there is a
function out there, with that particular signature that will sort the
array...

That does not strike me as a facility I would want. It seems like a lot of
special code to generate a wrapper for a very specific single line of code.

···

On Tue, Aug 16, 2016 at 11:31 PM, Charlie Monroe <charlie@charliemonroe.net> wrote:

On Aug 17, 2016, at 12:45 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Aug 16, 2016 at 5:35 PM, Anton Zhilin <antonyzhilin@gmail.com> > wrote:

What we need here is sort_by from Ruby. How about adding these
overloads?

func sort<U: Comparable>(by: (T) -> U)
func sorted<U: Comparable>(by: (T) -> U) -> [T]

I'd be in favor. We'd need another name, since the current closure
predicate is already standardized to `by:`.
(Or, as I propose above, once `Ordering` comes to the stdlib, we can just
have it as `func sort<U: Comparable>(_ ordering: Ordering, by: (T) -> U)`,
called like `sort(.ascending) { $0.age }`.)


(Xiaodi Wu) #9

Perfect. I use the phrase "sort on" often and was thinking of suggesting
that, but I was afraid it was just jargon in my line of work.

···

On Tue, Aug 16, 2016 at 19:17 Silvan Mosberger <infinisil@icloud.com> wrote:

I'd be in favor. We'd need another name, since the current closure
predicate is already standardized to `by:`.

Haskell uses "on" for sorting with a mapping. There are both

sortOn :: Ord
<http://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Ord.html#t:Ord> b
=> (a -> b) -> [a] -> [a]

and

sortBy :: (a -> a -> Ordering
<http://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Ord.html#t:Ordering>)
-> [a] -> [a]

in Haskell.


(David Rönnqvist) #10

Haskell also has a `comparing` function

    comparing :: (Ord a) => (b -> a) -> b -> b -> Ordering

which applies a function on both the left hand side and the right hand side to get two values that can be compared/ordered.
This makes the call site look something like this:

    sortBy (comparing length) names

The same can be done in Swift, resulting in a similar and very English-like calls site:

    names.sort(by: comparing { $0.characters.count })

That said. While this (the `comparing` function) is a fun exercise in higher order functions, a more Swifty syntax for this is probably a separate overload of the sort/ed/ function.

- David

···

On 17 Aug 2016, at 02:17, Silvan Mosberger via swift-evolution <swift-evolution@swift.org> wrote:

I'd be in favor. We'd need another name, since the current closure predicate is already standardized to `by:`.

Haskell uses "on" for sorting with a mapping. There are both

sortOn <> :: Ord <http://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Ord.html#t:Ord> b => (a -> b) -> [a] -> [a]

and

sortBy <> :: (a -> a -> Ordering <http://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Ord.html#t:Ordering>) -> [a] -> [a]

in Haskell.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Shawn Erickson) #11

I think a first class sort descriptor style would robustly solve most
complex sorting needs. It can deal with multiple sort dimensions, etc.
which an attribute wouldn't really solve without language complexity.

···

On Wed, Aug 17, 2016 at 1:12 AM David Rönnqvist <swift-evolution@swift.org> wrote:

Haskell also has a `comparing` function

    comparing :: (Ord a) => (b -> a) -> b -> b -> Ordering

which applies a function on both the left hand side and the right hand
side to get two values that can be compared/ordered.
This makes the call site look something like this:

    sortBy (comparing length) names

The same *can* be done in Swift, resulting in a similar and very
English-like calls site:

    names.sort(by: comparing { $0.characters.count })

That said. While this (the `comparing` function) is a fun exercise in
higher order functions, a more Swifty syntax for this is probably a
separate overload of the sort/ed/ function.

- David

On 17 Aug 2016, at 02:17, Silvan Mosberger via swift-evolution < > swift-evolution@swift.org> wrote:

I'd be in favor. We'd need another name, since the current closure
predicate is already standardized to `by:`.

Haskell uses "on" for sorting with a mapping. There are both

sortOn :: Ord
<http://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Ord.html#t:Ord> b
=> (a -> b) -> [a] -> [a]

and

sortBy :: (a -> a -> Ordering
<http://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Ord.html#t:Ordering>)
-> [a] -> [a]

in Haskell.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution