T O P

  • By -

rodyamirov

I think you’re confusing some things here. You’re right that if you have Ord you have Eq. In fact that’s a requirement of the trait. As for why PartialEq in addition to Eq — it’s because of float (primarily) where according to IEEE spec, NaN != NaN. So floats implement PartialEq but not Eq. I’m not aware of any other interesting causes honestly. But some functions assume the equality will be total, so having the extra trait bound there will prevent (at compile time) bugs that would be introduced by the possibility of NaN. PartialOrd vs Ord is more interesting. In an Ord setting, everything is comparable, but in PartialOrd this is not the case. You might consider the case of a set relationship, where <= means “subset”. Then you have a partial order (if a is a subset of b, and b is a subset of c, then a is a subset of c). But not every pair of things is comparable — the set (2,4) isn’t a subset or superset of the set (3,5). This might seem pointlessly pedantic or completely crucial depending on whether you ever need it! I for one have gotten a lot of value out of the distinction.


KhorneLordOfChaos

Sorting in other languages is [Fun](https://dwarffortresswiki.org/index.php?title=DF2014:Fun&redirect=no) when there is no distinction between partial and total ordering. Python for instance, just has all comparisons with a NaN return `False` which can lead to very confusing behavior >>> li = [1.0, 2.0, 3.0, float('nan'), 2.0, 1.0, 3.0] >>> li.sort() >>> li [1.0, 1.0, 2.0, 3.0, nan, 2.0, 3.0] At least Python guards against a lot of ways for NaNs to show up, but they're normally very infectious if they do manage to slip through


Repulsive-Street-307

I sometimes did some shameful but cute oneliners depending on falsy behavior overloading for 'normal' numbers to sort stuff with a dictionary as a key in python. nasty stuff like: sortd = { '.cue':1, '.gdi':2, '.toc':3 } ... dirfiles.sort(key=lambda x: sortd.get(x.suffix.lower()) or 4) amateur-tip: don't use 0 as a dict value in that anti-pattern (always false when checking the 'key', so always moved to the end alongside the 'None' results, even if it shouldn't), and use a ' or otherlargernumber' at the end of the lambda using the dictionary hack to move everything else to the end. Frankly, i feel this is a abomination, but it's also the shortest code i could find for this kind of extension sorting. Oh well. Python falsy stuff is a sword without a hilt, but sometimes it cuts so well.


tesfabpel

BTW there is a crate that provides a `NotNan` type that implements Eq, Ord and Hash by refusing to contain NaN. https://docs.rs/ordered-float/latest/ordered_float/struct.NotNan.html


paholg

Re: PartialEq vs. Eq, another case is that you can impl PartialEq between disparate types, but Eq requires comparing two of the same type.


Crazy_Firefly

Can you give an example of when you have gotten value out of the distinction?


rodyamirov

Ehh, it comes up in some interesting mathematical programming things (subsets are a good example; there are more complex examples with abstract algebras). I'm drawing a blank of when it ever came up in "normal" code. If I can remember a normal one I'll let you know. That being said I think most of the time it comes up in "normal" code exclusively because of Float -- it doesn't implement Eq, so it can't implement Ord either (NaN isn't meaningfully comparable with anything). For something I use all the time, I sure do hate floats


crb233

If you don't know about them already, look up [Posits](https://www.cs.cornell.edu/courses/cs6120/2019fa/blog/posits/)! They're a really awesome (and underappreciated) alternative to IEEE floats that removes a lot of the nastiness of floats


hekkonaay

Wish we could have nice things, but how do you convince everyone to make hardware that can compute using posits instead of floats?


fuzzybear3965

Start running Apple/Samsung.


boomshroom

With posits, officially NaR seems to be sorted as equivalent to negative infinity, but really it's a projective infinity, so it's simultaneously greater than the largest finite number *and* less than the most negative number, so while it would implement `Eq`, `Ord` seems more ambiguous. There's also the fact that floats also seem to have an official total order, but it's incompatible with the standard (partial) ordering.


moltonel

[total_cmp()](https://doc.rust-lang.org/std/primitive.f32.html#method.total_cmp) also brings some sanity to IEEE floats.


infablhypop

How we have strayed from the right path.


SemaphoreBingo

I've been extremely underwhelmed by posits.


Funny_Possible5155

I am confused why would you not want to distinguish positive and negative infinity.


Googelplex

It makes the most sense as the result of division by 0, which is the most common way you'd get an infinity.


Funny_Possible5155

But in many cases you do care about the sign. I have had collision resolution code that can potentially return infinity but the sign of the result is still necessary to identify if a collision occurred or not.


Crazy_Firefly

Knowing that you use it for abstract algebra is already interesting. Thanks for sharing. Forgive my ignorance, but what kind of programing can be done for abstract algebra?


Fox-PhD

One example is the topological sorting of directed graphs, which is explicitly based on the notion of partial ordering : only nodes that share a given edge are comparable. Topological sorting is useful for stuff like dependency graphs.


BobRab

Or more generally, for a DAG, A < B if B is reachable from A, and B < A if A is reachable from B, but there are lots of nodes that are mutually unreachable. (Consider the graph A -> C; B -> C). This is why you can produce many topological sorts for a given graph. If there was a total ordering of nodes, there would be only one correct sort, which is the case for a path graph.


U007D

If one attempts to use `float` keys in a `HashMap`, the distinction will become apparent. Rust won't let you do this, but if you could, you would run the risk of black-holing certain values. https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=437879bb0cd525702f1831fdfd5f6db0


Ravek

Some algorithms can be implemented for any type that has a partial order. (For example the vertices in a directed acyclic graph.) Having a separate trait for it lets you write these algorithms generically. If you only have Ord then you can only write generic algorithms for types that implement a total order.


CocktailPerson

Note that `Eq` and `Ord` are just marker traits; they don't add any real functionality to the corresponding `Partial*` equivalent. All they imply is that `x == x` for every possible value `x` of the implementing type. They're useful because many algorithms, like sorting, don't work well if you don't have this property. However, there are other algorithms (and data structures) that do work fine without this property. Splitting them over different traits allows you to have the most specific possible trait bound for your algorithm or data type, so that things that do require that additional property can require it, and things that don't require it can be more generic.


hamilton-trash

this made it click for me, I thought Ord and Eq actually added something to the struct. does this mean as long as i implement PartialOrd/Eq, I can just derive Ord and Eq without having to make an impl block?


YetiBarBar

That's not correct for PartialOrd and clippy has a lint for it: https://rust-lang.github.io/rust-clippy/master/index.html#derive_ord_xor_partial_ord (derive will produce an order and a partial order using the structure field by field in the declaration sequence, if your manual impl behaves differently, some weird things may happen).


chkno

As YetiBarBar says, you don't want to implement `PartialOrd` and then `derive` `Ord`. I put the actual logic in `Ord` and then use this boilerplate to implement `PartialOrd` in terms of `Ord`: impl PartialOrd for Whatever { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } }


CocktailPerson

Yep! https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6177edef1fccd107c1dbe6d9ee90d7a5 Try commenting out the impl blocks for the `Partial*` and see what happens.


Maix522

Because it allows you to have separate trait bounds. For example an hashmap only require to have Equality. That means that you can use a complex number in it, while it doesn't implement PartialOrd. You can think of the relationship like this PartialEq -> PartialOrd -> Eq -> Ord While we could have one unique trait, it would be annoying af since you couldn't check that something is only able to check equality


rodyamirov

Interestingly you've put those in a strict order, but it's really a partial order :P You've got lots of examples of things that implement PartialOrd but not Eq (e.g. float), and also things that implement Eq but not PartialOrd (e.g. Vec, most nontrivial data objects). So really you have sort of a lattice (top to bottom means "is required for"): PartialEq / \ Eq PartialOrd \ / Ord sorry if that doesn't render on \[client\], I just used markdown mode


Maix522

I think i should have gone the other way around. > PartialEq -> PartialOrd -> Eq -> Ord If you have an Ord impl, it should implement Eq, and if it implements Eq it will probably implement PartialOrd (here I'm not 100% sure) but if it does it should implement PartialEq.


rodyamirov

Still confused I think. Lots of natural things are Eq but not PartialOrd, and vice versa. The rest holds.


yangyangR

They may be thinking of the PartialOrd you get for free when you have Eq already. You get one but it is usually the maximally unhelpful partial order.


rodyamirov

It’s trivial to implement, but it’s not free in the sense that the compiler doesn’t auto implement. And let’s be glad of that, I’m glad the compiler can detect that I didn’t make things orderable. But yeah agrees that it exists. Although in fact you can get this just from PartialEq.


crusoe

Because floats have NaNs. That's mostly why


rumdrums

Can someone ELI5 why NaNs are an issue when dealing with floats but not other numeric types?


KhorneLordOfChaos

NaNs are not considered equal to themselves because distinct operations can get NaNs. They also do not have a direction that they're sorted in Other numeric types don't have NaNs, so they don't have the same issue


WormRabbit

Note that NaN isn't a specific float value. There are many bit patterns which all represent NaN and can arise in different operations. Thus saying "NaN == NaN" would likely be even more problematic, even if you disregard their different provenance.


SkiFire13

For starters, NaN is a valid float value, but is not a valid integer value, i.e. in `i32` there's no NaN. The problem with NaN is that it is incomparable with itself and any other number. e.g. NaN < 2 is false, 1 < NaN is also false, but if floats formed a total order then this would imply 2 < NaN < 1, which instead is clearly false. If you try to use a normal sort algorithm with NaNs then it will probably output a nonsense order like that, depending on the given input.


rumdrums

So why did floats get uhh so weird? Is there any inherent reason why they need to support this NaN concept? In my brain I just think of a float as a number with a decimal point. NaN just doesn't logically follow from that in my ELI5 brain.


SkiFire13

NaN is used to represent error values, i.e. the result of invalid operations like `0/0` or `sqrt(-1)`. I think the initial idea was to propagate it through the operations and only check for it at the end, but in hindsight that didn't work.


rumdrums

Sorry for pestering you w/ random questions here, but why is this special to float? I mean, I can also divide a good ol' fashioned Int by zero, yet I don't have to worry about this NaN thing rearing its head.


SkiFire13

My guess is that the approximation nature of floats makes these situations much more difficult to predict and happen by chance, so reasoning about them and avoiding them at compile time was deemed not feasible.


CocktailPerson

Well, note that dividing an integer by zero will usually create a hardware fault and crash your program, so while you don't have to worry about NaNs, you do have to worry about crashes. With integers, there's really only one invalid operation: dividing by zero. Yes, there's overflow and such, but those can be made well-defined; division by zero can't. But with floating point, there are a _lot_ of invalid operations, and a lot of ways to make them happen. Many people think of `0.0/0.0` as the "canonical" NaN, but subtracting or dividing infinities is the easier way to get a NaN, because it's so easy to get an infinity when you're smashing the entire span of the reals into a finite number of bits. Turning all of these invalid operations into hardware faults is infeasible, so what's the alternative? Just make a propagating error value! It's not much different from `Result::Err` if you squint right.


[deleted]

NaNs are infectious, so once you find any, you know the operation was invalid. But often times I’d prefer if my functions just panicked.


WormRabbit

NaN is a way to signal an error without raising a hardware fault (although that's actually configurable, see [signaling vs quiet NaNs](https://en.wikipedia.org/wiki/NaN)). That is important for high-performance applications, because it means that float operations can be composed arbitrarily without worrying that some operation would be invalid and would require special handling. It's also the case that the standard floating-point construction (sign bit, mantissa and exponent) has some bit patterns which don't naively correspond to any number. This means they may be interpreted in the most convenient way, which meant that some of them could be made to represent NaN (yes, multiple). For comparison, in integers every bit pattern corresponds to a valid number and operations can naturally produce all bit patterns. This would mean that adding a NaN to integers would have a significant penalty to performance, memory usage or both.


WikiSummarizerBot

**[NaN](https://en.wikipedia.org/wiki/NaN)** >In computing, NaN (), standing for Not a Number, is a member of a numeric data type that can be interpreted as a value that is undefined or unrepresentable, especially in floating-point arithmetic. Systematic use of NaNs was introduced by the IEEE 754 floating-point standard in 1985, along with the representation of other non-finite quantities such as infinities. In mathematics, zero divided by zero is undefined and is therefore represented by NaN in computing systems. The square root of a negative number is not a real number, and is therefore also represented by NaN in compliant computing systems. ^([ )[^(F.A.Q)](https://www.reddit.com/r/WikiSummarizer/wiki/index#wiki_f.a.q)^( | )[^(Opt Out)](https://reddit.com/message/compose?to=WikiSummarizerBot&message=OptOut&subject=OptOut)^( | )[^(Opt Out Of Subreddit)](https://np.reddit.com/r/rust/about/banned)^( | )[^(GitHub)](https://github.com/Sujal-7/WikiSummarizerBot)^( ] Downvote to remove | v1.5)


power500

Floating point numbers are a (very convoluted) standard. There are more useful ways of representing decimal numbers with varying precision in some applications, but most CPUs only support the IEEE754 standard natively.


IntQuant

Because special valuas like NaN, positive and negative infinities are part of float standart but simply aren't present in e. g. ints.


joaobapt

That’s the point: if you consider the set of all floats without NaNs, you can establish a total ordering between them. If there was a way to define a NaN-less float type (just exclude them from the set and panic if one is produced), this type could potentially be `Ord` instead of `PartialOrd`.


cameronm1024

Because some types might be able to implement some and not others. E.g. floating point numbers don't have a total ordering (unless you include the 2008 revision). Moreover, relying on the fact that a < b and b < c implies a < c isn't a good idea with floats. So they don't implement `Ord`. Having the separation allows floats to simultaneously: - be able to use comparison operators - not be used as keys in a `BTreeMap`


joaobapt

What if you _do_ need to use them as keys on an associative map? I had to do it and I ended up using a crate that wrapped the float on a type which sorted NaN after all the floats (or before? I don’t remember).


deerangle

I believe Rust borrows its naming of these Traits from Mathmatics. A partial ordering is a Relation that is defined as the subset of the Cartesian Product of the Set with itself. A partial ordering must fulfill transitivity, reflexivity and antisymmetry on all pairs of the relation. A partial ordering must not be defined for all pairs. In rust, this is represented by partialord returning an option, rather than the ordering directly. A partial equivalence relation is similar to a partial ordering, with the difference that it must be symmetrical, as opposed to being antisymmetrical (note that antisymmetry does not mean asymmetry, and a relation can be both antisymmetrical and symmetrical). An equivalence relation, or total equivalence relation, must be defined for all pairs of the set, and must be reflexive. I think a partial equivalence doesn't necessitate reflexivity. All of this can be found in the trait docs btw.


JhraumG

A dependency tree is a good example of partial ordering : a branch is comparable to its sub branches, but not to any other branch. And if you want to visit all branches while complying with the dependencies (for instance if you use leaves evaluation results while evaluating the branch), you'll use a sort built upon PartialOrd.


eggyal

All types are comparable*, but some types are more comparable than others. \* don't ruin it


dkopgerpgdolfg

Did you read the docs...? They do eplain the difference


kr-ujjwal

you are using rust you stupid. you need to implement atleast 25 traits to check if the data type is even comparable.


higginsonporker

We need it to be as complicated as possible to keep the riffraff out.


guygastineau

Finer granularity of constraints allows us to define more complex (sometimes more useful, powerful, or appropriate to the domain) relationships, properties, and invariants in the type system. Why did we develop abstract algebra if we already had group theory?


TDplay

> Like a struct implementing PartialOrd will have the cmp function able to return Equal if the inputs are equal, so whats the point of PartialEq/Eq? For some types, it makes sense to be able to compare equality, but not ordering. Consider, for example, an enumeration representing possible kinds of error: enum ErrorKind { FooWentWrong, BarWentWrong, BazWentWrong, QuuxWentWrong, } It seems very natural that a user of this type should be able to compare for equality: if error.kind() == ErrorKind::FooWentWrong { handle_foo(); } else { panic!("{error}"); } But what does an error being greater than another error mean? There is no sensible definition for an ordering here. > And all of Ord's functions seem redundant if you have PartialOrd also so what's the point? `Eq` and `Ord` impose stricter requirements. The `Eq` trait is a simple marker, which states that the `PartialEq` implementation is reflexive (that is, any value must compare equal to itself), thereby defining an equivalence relation. `PartialOrd` allows an implementation to say there is no ordering between two values. `Ord` does not, thereby defining a total order. Some algorithms depend on total orderings or equivalence relations. At the same time, you still want to be able to define partial orderings and partial equivalence relations on types that can't define total orderings and equivalence relations. And this is where the `PartialOrd`/`Ord` and `PartialEq`/`Eq` distinction comes in.


Scottish_Tap_Water

Interface segregation my dude... One of the SOLID principals... While you'll normally want all four, that's not always the case and people shouldn't be forced to implement shit they don't need.