Hacker News new | past | comments | ask | show | jobs | submit login

What is the expected result if you have NaN in your list?



In 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]


Ruby has it about right (imho):

  > [1.0,2.0,Float::NAN].sort
  ArgumentError: comparison of Float with Float failed


That'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.


Rust's way is more flexible though: You can choose how to treat NaNs, in Ruby you have to fail.


in 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]


That's just horrible. You just changed behavior globally.


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


Or use refinements.


No, 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).


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


Right, 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.


and the Rust equivalent would be to define a newtype

Which you can do in Ruby as well, as I pointed out just two comments below the one you're replying to.

The real advantage of Rust here was imho best explained by sanderjd; Rust can perform this check at compile time whereas in Ruby it's a runtime exception.


I don't have NaNs in my list. That's an invariant I would be happy to express in the type system.


Then you need another type. A float has NaNs and Rust knows it and prevents you from possibly shooting yourself in the foot.


A 'newtype wrapper' has your back in that situation, which lets you do exactly that.


Yep, 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.


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


Neat! My version with some mostly superficial changes[0].

Note that we haven't actually removed the panic in the `Ord` implementation! Which is because we've eliminated what we believe is the source of the ordering uncertainty (the NaN), but the type system still doesn't know that.


Whoops, didn't post the link: http://goo.gl/7AZxa6




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: