How does 'sort' order a list that holds a class and its subclass, and where do unset values go?
← Streams and Pipelines · Ref: Q2246
Values of different run-time types are never comparable in EK9 - their '<=>' is unset - so 'sort' cannot interleave them. It must still place every element, so it GROUPS them: by run-time type name, each group in the order of that type's own '<=>', and every unset element last, in the order it arrived. Here every Holder comes before every Named ('Holder' sorts before 'Named'), the Holders by count, the Nameds by count and then name, and the unset Holder at the end.
To interleave different kinds, give their ABSTRACT base or trait one hand-written sameness over what every kind provides (every Shape by area): that ordering is hierarchy-wide, so any two values compare (it must answer for every pair of set values: two values of one type that it leaves unset stop the sort with an EK9 Exception naming them). A built-in whose '<=>' holds only within a group of its own values sorts GROUPED the same way: Money by currency and Dimension by unit suffix, each group in order, the groups in code order ([10#GBP, 5#USD, 3#GBP] sorts to 3#GBP 10#GBP 5#USD). A value that cannot be compared with itself is kept with the unset ones. For any other order, sort with a comparator: 'sort by byCount' - it too must answer for every pair of set values, and one that answers unset stops the sort with the same Exception (Q2283).
Unset elements are kept by 'sort' and 'group': after a sort, 'group' puts every unset element into ONE final group, in input order, after the groups of set values. 'collect as List' keeps them too - a stream stage never drops an unset element, and a terminal that writes into a List appends with '+=' - so 'sort | collect as List of Holder' holds every element, the unset one last. Collecting into a Set, a Dict or an Optional, or piping into a List with its own '|', still ignores an unset element, and a 'filter' on the stream is how you remove them.
Example
#!ek9 defines module qa.streams.sort.mixed.types defines constant firstName <- "ada" secondName <- "bob" defines class Holder as open count as Integer: Integer() Holder() -> start as Integer this.count: start tally() as pure <- rtn as Integer: count operator $ as pure <- rtn as String: `Holder(${count})` default operator <=> default operator #? default operator ? Named extends Holder name as String: String() Named() -> start as Integer label as String super(start) this.name: label override operator $ as pure <- rtn as String: `Named(${tally()}, ${name})` default operator <=> default operator #? default operator ? defines function shown() -> held as Holder <- rtn as String: held? <- $held else "unset" defines program SortMixedTypes() stdout <- Stdout() holders as List of Holder: [Named(2, secondName), Holder(3), Holder(), Holder(1), Named(1, firstName)] cat holders | sort | map with shown > stdout
Other ways to ask this
- Why does sort put all the Holders before all the Nameds?
- How does EK9 sort values of different run-time types?
- Where does sort put unset elements?
- Why are my subclass values not interleaved with the super class values after a sort?
Coming from another language?
Java: Collections.sort over a List<Holder> uses the super class's compareTo for every element, so a subclass that refines equality is 'inconsistent with equals'; a compareTo that throws for an unrelated subtype makes the sort throw. Kotlin: the same. EK9: '<=>' answers unset for different run-time types, and sort groups by type instead of failing.
Keywords: <=>, comparator, mixed types, run-time type, sameness, sort, stream, subtype, unset