dart-lang / language

Design of the Dart language
Other
2.62k stars 200 forks source link

Records with (comparable) positional fields should implement Comparable #3602

Open Joshix-1 opened 5 months ago

Joshix-1 commented 5 months ago

The following both should work as String and int can be compared to each other.

print(("a", 1) > ("a", 0));  // false
print(("a", 1).compareTo(("a", 0)));  // 1

Records should be compared by comparing the fields from left to right until one value isn't equal to the other.

int compare<
  A extends Comparable<X>,
  B extends Comparable<Y>,
  X,
  Y
>(A a, B b, X x, Y y) {
  return (a, b).compareTo((x, y));
}

should return the same as

int compare<
  A extends Comparable<X>,
  B extends Comparable<Y>,
  X,
  Y
>(A a, B b, X x, Y y) {
  final axCompare = a.compareTo(x);
  if (axCompare != 0) {
     return axCompare;
  }
  return b.compareTo(y);
}

This would be really useful for sorting a list of records.

final list = [("a", 1), ("b", 1), ("a", 0), ("b", -1)];
list.sort();
print(list);  // (a, 0), (a, 1), (b, -1), (b, 1)
lrhn commented 5 months ago

Very unlikely to happen. Just because two field types of a record type each have a natural ordering, that doesn't imply that the record type itself has exactly one natural ordering.

To extend a per-field ordering to a total ordering, it's most likely necessary to pick an order on the fields. That choice isn't necessarily given. For (A, B) we can choose to order by A first, then by B if equal As. But why not the other way around? And for a type like ({A mountain, B thunder}), it's decidedly unclear which one should be first.

It's always possible to sort or order things by an explicit comparator function, and unless the natural, inherent ordering is incredibly obvious, I think it's better to be explicit. (If I started from scratch today, I wouldn't make double and String be Comparable. Pretty much just int. Or maybe just not have Comparable at all.)

So, a no from me.

Joshix-1 commented 5 months ago

Rust and Python both allow comparing tuples to each other.

And for a type like ({A mountain, B thunder}), it's decidedly unclear which one should be first.

That's why I think it should only be for Records with positional fields.

For (A, B) we can choose to order by A first, then by B if equal As. But why not the other way around?

Because we read from left to right and the code goes from left to right and it's the natural order.

It's always possible to sort or order things by an explicit comparator function

That can be pretty annoying to implement every time.

munificent commented 4 months ago

To extend a per-field ordering to a total ordering, it's most likely necessary to pick an order on the fields. That choice isn't necessarily given.

I agree that it might do the wrong thing sometimes, but lexicographical in positional field order seems reasonable to me and probably what users want >90% of the time. I do think it would be handy if records could be sorted when their fields are comparable.

And for a type like ({A mountain, B thunder}), it's decidedly unclear which one should be first.

We could say that records with named fields aren't comparable. That would still cover the common case where you just want to sort a tuple with a couple of positional fields.

lrhn commented 4 months ago

I think I'd rather provide 5-10 comparator functions that people can use, like Comparator.record5.

interface class Comparator<T> { 
  // ...
  static int record2<T1 extends Comparable, T2 extends Comparable>((T1, T2) v1, (T1, T2) v2) {
    int result = v1.$1.compareTo(v2.$1);
    if (result != 0) return result;
    return v1.$2.compareTo(v2.$2);
  }

  static int record3<T1 extends Comparable, T2 extends Comparable, T3 extends Comparable>((T1, T2, T3) v1, (T1, T2, T3) v2) {
    int result = v1.$1.compareTo(v2.$1);
    if (result != 0) return result;
    result = v1.$2.compareTo(v2.$2);
    if (result != 0) return result;
    return v1.$3.compareTo(v2.$3);
  }
  // ...

If someone wants to sort bigger records of all comparable types, they should probably be using a list.

No language feature required.

If the order isn't right, we could also provide 2^n shuffle functions: Record.swap2134 would swap the first two fields of a quadtuple.

I might be influenced by not really liking the Comparable type. I very, very rarely find that a non-integer type really has an inherent ordering. (Strings being ordered lexically by code unit is a good example of a completely arbitrary order, which just happens to be compatible with lexical ordering of ASCII letters of the same case.)

FMorschel commented 1 month ago

I might be influenced by not really liking the Comparable type. I very, very rarely find that a non-integer type really has an inherent ordering.

At my company, we have a subtype of Comparable that is called DataTableComparable in which we accept as named parameters index and ascending (with defaults set by that exact type, for example, index 1 and ascending false, but for another class that may be index 0 and ascending true) so that when our DataTables asks to sort the data, we already have a way of sorting that properly.

I do agree that few things have an "inherent ordering" but they may have a "default ordering" of sorts.