Would it be possible/practical/desirable to split PartialEq/PartialOrd/Eq/Ord
into separate traits that implement the various operators (==, !=, >, >=, <, <=) individually? The operators could be overloaded and return different results depending on an Output
associated type. You could choose to implement PartialOrd/PartialEq
on types that implement all the required traits and have type Output = bool;
For example, here's how PartialEq
and Eq
could be written:
trait Equal<Rhs = Self>
where
Rhs: ?Sized,
{
type Output;
fn eq(&self, other: &Rhs) -> Self::Output;
}
trait NotEqual<Rhs = Self>
where
Rhs: ?Sized,
{
type Output;
fn ne(&self, other: &Rhs) -> Self::Output;
}
trait PartialEq<Rhs = Self>: Equal<Rhs, Output = bool> + NotEqual<Rhs, Output = bool>
where
Rhs: ?Sized,
{
// could provide a stub eq & ne functions that call Equal::eq and NotEqual::ne
}
trait Eq: PartialEq<Self> {}
If #![feature(associated_type_defaults)]
was enabled, the traits could provide a reasonable default value of type Output = bool;
. The function name could also be more general (eg. Equal::op
and NotEqual::op
instead of Equal::eq
and NotEqual::ne
) if preferable.
I won't post the full code for the PartialOrd
and Ord
traits, but they could be written in a similar way:
trait PartialOrd<Rhs = Self>:
PartialEq<Rhs>
+ GreaterThan<Rhs, Output = bool>
+ GreaterOrEqual<Rhs, Output = bool>
+ LessThan<Rhs, Output = bool>
+ LessOrEqual<Rhs, Output = bool>
where
Rhs: ?Sized,
{
// Required method
fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
}
trait Ord: PartialOrd<Self> {
// Required method
fn cmp(&self, other: &Self) -> Ordering;
// Provided methods
# snip #
}
Implementing these traits for a type would be straightforward:
impl Equal for i32 {
type Output = bool; // line not needed if associated_type_defaults used
fn eq(&self, other: &i32) -> bool {
*self == *other // might need to change if this trait really did implement `==`
}
}
impl NotEqual for i32 {
type Output = bool;
fn ne(&self, _other: &i32) -> bool {
!Equal::eq(self, _other)
}
}
impl PartialEq for i32 {}
impl Eq for i32 {}
The pain would be in transitioning between the designs and to reduce the pain it could require deprecations, dummy methods and perhaps auto assisted changes to manually written code. Any derived PartialEq/PartialOrd/Eq/Ord
should be able to just produce different output and be a transparent change.
Another alternative design could be that Equal/NotEqual/GreaterThan/GreaterOrEqual/LessThan/LessOrEqual
traits are only allowed to be implemented on types that don't implement PartialEq/Eq/PartialOrd/Ord
and both sets of functions override the operators, and although it would be less disruptive to existing code, personally I don't like this method. There may also be other ways to implement this that I haven't thought of. At first I thought about suggesting to add an associated type to the existing PartialEq/PartialOrd traits, but it didn't make much sense for these types to return anything but bool as these traits are really only about determining a partial/total order.
My initial motivation comes from wanting to be able to define relationships between mathematical expressions in a computer algebra system a clear way. Instead of using lots of method calls like this:
let x = Symbol::new('x');
let solution = solve([(x + 2).equals(x.pow(2)), (x).greater_than(4), (x).less_or_equal(20)], [x]);
//or
let solution = solve([(x + 2).eq(x.pow(2)), (x).gt(4), (x).le(20)], [x]);
With overloadable relationship operators it would be possible to overload the operators between expressions to return a Relationship
enum (which holds both left and right expressions) instead of a bool
and tidy up the design:
let x = symbol('x');
let solution = solve([x + 2 == x.pow(2), x > 4, x <= 20], [x]);
// or, if && and || were overloadable, then perhaps:
let condition = (x + 2 == x.pow(2)) && (x > 4) && (x <= 20);
let solution = solve(&condition, [x]);
// ps. where's the discussion about the design of
// the && and || short circuit operators mentioned in the docs?
Operator overloading for ==, !=, >, >=, <, <= without the restriction of returning bool
and the consistency requirements could perhaps be useful in other situations too, although maybe it could lead to some exotic looking code if abused.