Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does PartialEq return a bool, when PartialOrd is more nuanced and returns Option<Ordering>?

Tags:

equality

rust

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)?

like image 763
NO WAR WITH RUSSIA Avatar asked Jul 25 '21 02:07

NO WAR WITH RUSSIA


People also ask

What is Partialeq?

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.

What is PartialOrd in Rust?

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.


2 Answers

I can think of a few reason's why these traits are defined the way they are:

  1. PartialEq is the trait for the == operator. If it were to return an Option, language ergonomics around ifs and other control flows would need to at least be reconsidered.

  2. 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).

  3. 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.

like image 168
kmdreko Avatar answered Oct 16 '22 18:10

kmdreko


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".

like image 35
Silvio Mayolo Avatar answered Oct 16 '22 18:10

Silvio Mayolo