I'm not sure I follow. PartialOrd
isn't magical, it is just a trait, and it is the API that libstd
uses to interface with the standard collections.
If you want to implement different orderings for your types, just do exactly what PartialOrd
does:
- add your own traits for your own orderings, e.g.,
MyOrdTrait
- your own proc macros to automatically derive them, e.g.,
derive(MyOrdTrait)
- add small wrapper that maps a
T: MyOrdTrait
intoPartialOrd
, e.g.,struct Wrapper<T: MyOrdTrait>(T)
with aimpl<T: MyOrdTrait> PartialOrd for Wrapper<T>
, to be able to interface with libstd.
With this, your Person
example is solving by adding a derive(MyOrdTrait)
to Person
.
If your type's comparison semantics really are non-trivial, add methods, rather than overloading expectations
My expectation and that of many of my users is that a < b
works for types for which it is a meaningful operation, even if those types do not have a partial ordering relation.
ISO C++, in my view, is not a great example of a principled design process.
I'm confused with what you are trying to convey with your C++ examples. C++ supports types that have all kinds of ordering relations by allowing users to specify the relational operators however they want, and also supports convenience notation for some common ordering relations via the <=>
operator. If you choose <=>
, you can't specialize the operators.
Rust is quite similar, in that if you have a common ordering relation like a strict or non-strict partial order, or a total order, you can just write partial_eq
and partial_cmp
and call it a day. If you have an ordering relation that isn't one of this, but the result of the operators is bool
, you can "hack" it in by, e.g., manually specifying PartialOrd::{lt, le, gt, ge}
. But if the result isn't bool
, you can't use the operators at all, and that's again the expectation of the users of those types.
Why? In my view, it is a mistake to decouple the six comparison operators from each other,
As far as I can tell, the motivation for this can be solved in other ways that don't involve decoupling the six comparisons. [...]
WeakCmp
The expectation, of course, is that for SIMD, the compiler is able to aggressively inline these and macro-fuse the resulting calls to
std::arch
intrinsics in the obvious manner, so that!(a < b)
reduces toa >= b
at the instruction level.
For a performance oriented feature, such optimizations must either be guaranteed, or a escape hatch must be available. This is why users are allowed to overwrite the PartialOrd::{lt, le, gt, ge}
operations.
If instead of 6 different traits, you prefer a single Trait
with 6 different methods to implement, that would be fine with me.
When you propose WeakCmp
it feels like you are proposing this to be a different type of ordering relation. It is unclear what ordering relation that would be, and how it could work for SIMD vectors (or subset relations, for example).
I don't think they should have been
PartialOrd/Eq
in the first place
This is offtopic, but floats form a strict partial order, and PartialOrd
is the libstd API that collection use to require types with either a strict or a non-strict partial order, so this sounds appropriate to me. Floats also have a total order, but for floats this order is different from their strict partial order, so a problem of the current API is that this use case is not supported. AFAICT that's orthogonal to this discussion.
I'd like to share here that in numpy the behavior of comparison operators is indeed terribly unexpected to students (I teach computational physics), and is in my opinion one of the worst features of the DSL. Students who make the mistake of comparing arrays inevitably need help up extricate them from the situation. The result ends up in an if or while statement, and the resulting error message gives them terrible advice.
You are correct in that this is a real issue new numpy users have, but I disagree in the relational operator overloads being the cause of this issue, because new users of other linear algebra libraries, like Eigen
, do not have this problem. The main difference is that Eigen
is strongly typed, while numpy
isn't. This means that when a new user of Eigen
tries to assign the result of comparing two arrays with a < b
to a bool
, they get a nice and obvious compiler error, while numpy
user get, as you put it, "terrible advice".