6 ms·
I too have suffered Rust's religiosity on the floating point issue. In Python, given an array xs = [3.1, 1.2, 4.3, 2.2], I can write xs.sort() and get [1
by alextgordon 12y ago
I too have suffered Rust's religiosity on the floating point issue.
In Python, given an array xs = [3.1, 1.2, 4.3, 2.2], I can write
xs.sort()
and get [1.2, 2.2, 3.1, 4.4]
In Haskell
sort xs
In Swift
sort(&xs)
In Rust you have to spew this monstrosity
xs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Less))
The Rust position appears to be that sorting an array of floats is unreasonable and so you must be "punished" by not being allowed to use the built-in .sort() function.
- heinrich5991 12y agoWhat is the expected result if you have NaN in your list?
- lclarkmichalek 12y agoIn python at least, it's a bit funky: >>> sorted(map(float, ['1', '2', 'nan', '4', '3'])) [1.0, 2.0, nan, 3.0, 4.0] >>> sorted(map(float, ['1', '5', '2', 'nan', '4', '3'])) [1.0, 2.0, 3.0, 4.0, 5.0, nan]
- moe 12y agoRuby has it about right (imho): > [1.0,2.0,Float::NAN].sort ArgumentError: comparison of Float with Float failed
- whyever 12y agoRust's way is more flexible though: You can choose how to treat NaNs, in Ruby you have to fail.
- moe 12y agoin Ruby you have to fail Oh, really? ;) > [1.0,2.0,Float::NAN].sort ArgumentError: comparison of Float with Float failed > # let's make NaN sortable > Float::NAN.class.send(:define_method, '<=>') { |x| -1 } > [1.0,2.0,Float::NAN].sort => [1.0, 2.0, NaN]
- the_mitsuhiko 12y agoThat's just horrible. You just changed behavior globally.
- moe 12y agoI didn't say it's pretty. Merely chose that example to point out the absurdity of challenging Ruby on the grounds of flexibility (of all things). Obviously, in a real program you'd rather write a custom sort-comparator, use a wrapper-class, or monkey-patch only the specific NaN instances that you want to change the behaviour of.
- bshimmin 12y agoOr use refinements.
- klibertp 12y agoNo, it's beautiful. Being a Python programmer it took me a long time to appreciate the power to do things like this. Learning Elisp, Smalltalk, Io and JavaScript (well) certainly helped. Also, of course you can scope such a change however you want, for example to a single block (and with threadlocals it's going to be mostly safe).
- dkarapetyan 12y agoHe was just demonstrating that it is possible. You can in fact alias the method, do the sorting, and then restore the previous functionality of throwing errors. Plus, someone already mentioned that you can also use refinements.
- lambda 12y agoRight, and the Rust equivalent would be to define a newtype that wraps your floating point type and defines the ordering semantics you want; in Ruby, you have changed the behavior for everything that uses Floats (including other libraries that may depend on this behavior), while in Rust you can use your newtype, other libraries can use standard floating point behavior, and you won't have any confusion about who wants which semantics.
- sanderjd 12y agoThat's definitely the right behavior for Ruby—it catches a contract violation and fails at runtime, which is idiomatic. What's nice about Rust's approach is that it catches the possibility of that contract violation at compile time, and forces you to decide what to do about it before the code ever runs. The right thing to do in Ruby would be to catch that exception and handle it in some fashion, but there is no indication at the time you're writing the code that the possibility exists, so you're unlikely to handle it unless you have a strong awareness of the issue with floats and NaN. Rust encodes that awareness into the language itself, which actually limits the expertise you need to write good code.
- alextgordon 12y agoI don't have NaNs in my list. That's an invariant I would be happy to express in the type system.
- kaoD 12y agoThen you need another type. A float has NaNs and Rust knows it and prevents you from possibly shooting yourself in the foot.
- steveklabnik 12y agoA 'newtype wrapper' has your back in that situation, which lets you do exactly that.
- sanderjd 12y agoYep, and that type can have a total order and work with the `sort` method with no further ceremony. That actually might be a nice type to have in the standard library. It seems like it would be widely useful, but I'm not sure where it would fit in on cargo.
- twic 12y agoI had a crack at this. This is about the third Rust program i've ever written, so it's probably chock full of noob mistakes: #![feature(std_misc)] mod natural { use std::num::Float; use std::iter::IntoIterator; use std::iter::FromIterator; use std::cmp::Ord; use std::cmp::Ordering; #[derive(PartialEq, PartialOrd, Debug)] pub struct Natural(f64); impl Natural { pub fn new(value: f64) -> Option<Natural> { match value { x if Float::is_nan(x) => None, _ => Some(Natural(value)) } } pub fn new_all<'a, A: IntoIterator<Item=&'a f64>, B: FromIterator<Natural>>(values: A) -> B { let b: B = values.into_iter().map(|f| Natural::new(*f).unwrap()).collect(); b } } impl Eq for Natural { } impl Ord for Natural { fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other).unwrap() } } } use natural::Natural; fn main() { let fs = [3.0, 1.0, 1.0]; let mut xs: Vec<Natural> = Natural::new_all(&fs); println!("before = {:?}", xs); xs.sort(); println!("after = {:?}", xs); } In particular, the assignment of the return value of new_all to a local is ugly, but i couldn't figure out how to please the type checker without it.
- Peaker 12y agoI don't know much Rust. But your comment just made me appreciate Rust a lot more! To me, this signals that they value long-term safety over short-term convenience. Any other choice, IMO and IME, is short-sighted.
- oscargrouch 12y agoLets see after you try a big project in Rust, if you come back with the same appreciation. Most of the times i think is a overburden, I know people like Haskellers like to suffer and are masochistic coders, and may like this, but i dont.. already have to deal with C++, and Rust did achieve the impossible.. Its even more over-enginnered than C++, and its not even faster
- Ygg2 12y agoYou mean a large project like Servo, or large project like Rust compiler itself?
- steveklabnik 12y ago> Its even more over-enginnered than C++, Can you explain more about what you mean by 'over-engineered' here? > and its not even faster We are faster sometimes, and we haven't even put time into optimizing things. We're also slower sometimes.
- tshepang 12y agoI think you should replace 'time' with 'a lot of time'. I've seen several commits that optimize things.
- Peaker 12y agoIME, us "masochists" suffer much less in the end. We "suffer" a few seconds here and there when we get a compiler warning, forcing us to think edge cases through. Then, later, we suffer much less through runtime debugging, QA, and get a lot less calls at 2AM.
- Ygg2 12y agoWhenever I've seen people complaining about Rust religiosity, is mostly because other languages trained them to be careless about certain things. Rust cares great deal about PartialEq and Eq because you can have stuff like non-deterministic float NaN sorting or Run-time errors as demonstrated by previous posts. Or Rust cares about doing manual memory management safely, but since you are arriving from either C/C++ which cares little about doing it safely, or GC language which cares little about memory management, you find it obnoxious. Hell, even the TFA mentions how hard it is to use Hash, I'm pretty sure that's because Rust is aiming for actually making the hash work correctly and not allow you to just randomly compromise yourself.
- alextgordon 12y agoPerhaps in some cases, but how does that apply here? People won't stop needing to sort arrays just because the syntax is made heavier and obscure. It's fine to punish people for coding in the wrong way, but here I have to accomplish a task (sorting the array) and so the punishment serves no purpose at all except for making my experience worse.
- Ygg2 12y ago> It's fine to punish people for coding in the wrong way, but here I have to accomplish a task (sorting the array) and so the punishment serves no purpose at all except for making my experience worse. Then Rust way is fine. It's merely 'punishing' you for not thinking things through at compile time (that floats are sortable), instead of at run time (dependent on input). Maybe you prefer to have fail dynamically, and that's fine, but that's preference, and the goals of Rust do not align with it. If you find relying on it often, why not write a macro? So: xs.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Less)) becomes: sort!(xs) Also honestly, if this is a very common use case bring it up on Rust forums maybe people will add a macro. They added macro for generating N elements for a vector.
- twic 12y agoWhy a macro? Why not just define that comparator as a function? fn value_nans_last<T: Float>(a: &T, b: &T) -> Ordering { match (a, b) { (x, y) if x.is_nan() && y.is_nan() => Ordering::Equal, (x, _) if x.is_nan() => Ordering::Greater, (_, y) if y.is_nan() => Ordering::Less, (_, _) => a.partial_cmp(b).unwrap() } } xs.sort_by(value_nans_last);
- whyever 12y agoIn Python you basically get undefined behavior when encountering NaNs while sorting, Rust let's you choose. This seems much more reasonable to me.
- falcolas 12y agoTo be fair, you also have the choice of implementing your own comparison function in Python (and likely Haskell, Ruby, etc) if the position of NAN actually matters to your algorithm.
- Peaker 12y agoYou're unlikely to know there's a problem and do so. Rust is reminding you of this fact, which is very nice.