It seems asymmetric and basic, so I want to understand the reasoning of this, why is that PartialOrd
defines partial_cmp
, to return Option<Ordering>
;
fn partial_cmp(&self, other: &Rhs) -> Option<Ordering> {}
And PartialEq
defines eq
to return bool
fn eq(&self, other: &Rhs) -> bool {}
Why doesn't PartialEq::eq
return Option<Eq>
where None
represents things that can't be determined to be equal? Why does PartialEq
get to skirt having the knowledge of which values can not compared for equality and thus which prevent the implementation of a full Eq
trait, and yet PartialOrd
has to know which values can not be compared for a full ordering (so it can return that information to the user as None
)?
Trait std::cmp::PartialEq1.0. Trait for equality comparisons which are partial equivalence relations. This trait allows for partial equality, for types that do not have a full equivalence relation.
PartialOrd only requires implementation of the partial_cmp method, with the others generated from default implementations. However it remains possible to implement the others separately for types which do not have a total order. For example, for floating point numbers, NaN < 0 == false and NaN >= 0 == false (cf.
I can think of a few reason's why these traits are defined the way they are:
PartialEq
is the trait for the ==
operator. If it were to return an Option
, language ergonomics around if
s and other control flows would need to at least be reconsidered.
When considering whether values are equal or not, "non-comparable" values fall into the latter category. There need not be a third answer; either they're equal or they're not. This is the same for >
, >=
, <
, and <=
provided by PartialOrd
, they all return bool
regardless if the values are "non-comparable" (they return false
in that case).
The meaning of "Partial" in the types differ. "Partial" in PartialOrd
means that types may not have a total order. Whereas "Partial" in PartialEq
means types may not have complete equivalence relations (may not be reflexive, symmetric, or transitive). The naming is similar because they both cover cases where types don't behave nicely, but they are conveying slightly different concepts. The APIs do not need to be identical.
Because PartialOrd
is cheating a bit, by mathematical standards.
Abstractly speaking, a mathematical structure is defined by two things: some operations and some axioms associated to those operations. In a programming language, we can define traits (or typeclasses, or whatever your favorite language calls them) which embody mathematical structures. We define the trait to support certain operations and simply trust that the programmer follows the axioms.
Total orderings (i.e. Ord
) and partial orderings (i.e. PartialOrd
) are usually modeled using a single operation: <=
. And the difference in the two structures is based on what properties we require of <=
. Likewise, partial equivalence relations (PartialEq
) and equivalence relations (Eq
) are defined in terms of ==
, and the two differ only in what properties.
If we were to define PartialOrd
and Ord
in this way in Rust, it would look something like (I'm omitting the RHS
type argument for simplicity reasons)
trait PartialOrd : PartialEq {
fn less_or_equal(self, other: Self) -> bool;
}
trait Ord : PartialOrd {}
which looks more like what the PartialEq
/ Eq
dichotomy looks like. Rust chose to implement PartialOrd
in terms of another operator: the trichotomy operator partial_cmp
and cmp
. In any total ordering, we can define an operator cmp(a, b)
which returns whether a
is smaller than, equal to, or larger than b
. And in a total ordering, exactly one of those is true. In a partial ordering, however, we can still define this operator partial_cmp(a, b)
, but there's a fourth option, namely that the two are simply not comparable.
As for why Rust did it this way, I couldn't say for certain. Haskell has a similar setup, with an Ord
typeclass, and in Haskell you can implement either <=
or compare
, and you get the other one for free. Python used to use __cmp__
(which is basically Rust's cmp
) but Python 3 switched completely to defining <=
and using functools.total_ordering
for comparisons.
We could debate back and forth which mechanism is better. cmp
has its benefits in terms of clarity and symmetry, but <=
is better for studying abstract mathematics. If you define both structures the mathematical way (==
for PartialEq
and <=
for PartialOrd
), then in both cases you get a single binary operator which "returns" bool
, and all of the other differences are in the axioms. The reason we don't have that satisfying symmetry in Rust is that Rust's PartialOrd
is defined in terms of an operator that only makes total mathematical sense in Ord
; we've taken a total ordering operator and removed some of its legs, leaving an operator that only works "some of the time".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With