Weird elusive bug using Comparable

I have this class:

class SimpleDate
  include Comparable

  VALS = [:year, :month, :day]
  attr_accessor *VALS

  def initialize(year, month, day)
    @year = year
    @month = month
    @day = day
  end

  #ideas on making this much shorter are welcome
  def <=>(other)
    VALS.each do |key|
      v1 = self.send(key)
      v2 = other.send(key)

      result = (v1 <=> v2)
      if result != 0
        return result
      end
    end
    0
  end
end

that represents a date that might have any of the three parts not
existant. For some reason date1 == date2 returns nil when done like
this:

class SimpleDateTest < Test::Unit::TestCase
  def test_comparison_with_nils
    date1 = SimpleDate.new(1,nil,nil)
    date2 = SimpleDate.new(1,nil,nil)

    assert_not_equal date1.object_id, date2.object_id
    assert date1 == date2
  end
end

The code shouldn't even work because nil doesn't implement <=>. What
the hell is comparable doing behind my back?

I thought date1 == date2 was the same as (date1 <=> date2> == 0 but
apparently not.

Pedro.

Pedro Côrte-Real wrote:

#ideas on making this much shorter are welcome
def <=>(other)
   VALS.each do |key|
     v1 = self.send(key)
     v2 = other.send(key)

     result = (v1 <=> v2)
     if result != 0
       return result
     end
   end
   0
end
end

How about something like this (untested):

def <=>(other)
   VALS.map{ |key| self.send(key) } <=> VALS.map{ |key| other.send(key) }
end

Pedro Côrte-Real wrote:

I have this class:

class SimpleDate
include Comparable

VALS = [:year, :month, :day]
attr_accessor *VALS

def initialize(year, month, day)
   @year = year
   @month = month
   @day = day
end

#ideas on making this much shorter are welcome
def <=>(other)
   VALS.each do |key|
     v1 = self.send(key)
     v2 = other.send(key)

     result = (v1 <=> v2)
     if result != 0
       return result
     end
   end
   0
end
end

# shorter
SimpleDate = Struct.new :year, :month, :day do
   include Comparable

   def <=> (other)
     self.class.members.each do |field|
       result = send(field) <=> other.send(field)
       return result unless result == 0
     end
     0
   end
end

that represents a date that might have any of the three parts not
existant. For some reason date1 == date2 returns nil when done like
this:

class SimpleDateTest < Test::Unit::TestCase
def test_comparison_with_nils
   date1 = SimpleDate.new(1,nil,nil)
   date2 = SimpleDate.new(1,nil,nil)

   assert_not_equal date1.object_id, date2.object_id
   assert date1 == date2
end
end

The code shouldn't even work because nil doesn't implement <=>. What
the hell is comparable doing behind my back?

I thought date1 == date2 was the same as (date1 <=> date2> == 0 but
apparently not.

No, it's differently implemented.

Kind regards

  robert

The code shouldn't even work because nil doesn't implement <=>. What
the hell is comparable doing behind my back?

Try it with 1.9

Guy Decoux

How then? According to the docs if you implement <=> and include
Comparable you get ==. But my second assertion is failing when it
should be blowing up when trying to do nil <=> nil.

Pedro.

···

On 7/31/06, Robert Klemme <shortcutter@googlemail.com> wrote:

> I thought date1 == date2 was the same as (date1 <=> date2> == 0 but
> apparently not.

No, it's differently implemented.

Good idea. I'm gonna try it.

Pedro.

···

On 7/31/06, Robin Stocker <robin@nibor.org> wrote:

How about something like this (untested):

def <=>(other)
   VALS.map{ |key| self.send(key) } <=> VALS.map{ |key| other.send(key) }
end

I don't have one around. Has this changed? I'd like to make this work
now. I thought implementing <=> and including Comparable gave me ==
but something else seems to be going on.

Pedro.

···

On 7/31/06, ts <decoux@moulon.inra.fr> wrote:

> The code shouldn't even work because nil doesn't implement <=>. What
> the hell is comparable doing behind my back?

Try it with 1.9

I tried it like this:

  def <=>(other)
    p 'At the start'
    result = VALS.map{|key| self.send(key)} <=> VALS.map{|key| other.send(key)}
    p 'At the end'
    result
  end

And the strange thing is that 'At the end' isn't printed but 'At the
start' is. How can this be?

Pedro.

···

On 7/31/06, Pedro Côrte-Real <pedro@pedrocr.net> wrote:

On 7/31/06, Robin Stocker <robin@nibor.org> wrote:
> How about something like this (untested):
>
> def <=>(other)
> VALS.map{ |key| self.send(key) } <=> VALS.map{ |key| other.send(key) }
> end

Pedro Côrte-Real wrote:

> I thought date1 == date2 was the same as (date1 <=> date2> == 0 but
> apparently not.

No, it's differently implemented.

How then? According to the docs if you implement <=> and include
Comparable you get ==. But my second assertion is failing when it
should be blowing up when trying to do nil <=> nil.

You get == but if you implement it yourself your version will shadow the one from Comparable:

>> class Foo
>> def <=>(o) 0 end
>> include Comparable
>> end
=> Foo
>> Foo.instance_method :==
=> #<UnboundMethod: Foo(Comparable)#==>

Now we add it to Foo:

>> class Foo
>> def ==(o) false end
>> end
=> nil
>> Foo.instance_method :==
=> #<UnboundMethod: Foo#==>
>> Foo.ancestors
=> [Foo, Comparable, Object, Kernel]

Kind regards

  robert

···

On 7/31/06, Robert Klemme <shortcutter@googlemail.com> wrote:

And the strange thing is that 'At the end' isn't printed but 'At the
start' is. How can this be?

Comparable#== catch StandardError

moulon% /usr/bin/ruby -v -e 'p NameError.ancestors'
ruby 1.8.4 (2005-12-24) [i486-linux]
[NameError, StandardError, Exception, Object, Kernel]
moulon%

moulon% ruby -v -e 'p NameError.ancestors'
ruby 1.9.0 (2006-07-14) [i686-linux]
[NameError, ScriptError, Exception, Object, Kernel, BasicObject]
moulon%

Guy Decoux

Ah, right, the exception is being caught. Here's a working version then:

def <=>(other)
  VALS.map{|key| self.send(key)||0} <=> VALS.map{|key| other.send(key)||0}
end

Much cleaner than my first and this time it actually works.

Pedro.

···

On 7/31/06, ts <decoux@moulon.inra.fr> wrote:

> And the strange thing is that 'At the end' isn't printed but 'At the
> start' is. How can this be?

Comparable#== catch StandardError

moulon% /usr/bin/ruby -v -e 'p NameError.ancestors'
ruby 1.8.4 (2005-12-24) [i486-linux]
[NameError, StandardError, Exception, Object, Kernel]
moulon%

moulon% ruby -v -e 'p NameError.ancestors'
ruby 1.9.0 (2006-07-14) [i686-linux]
[NameError, ScriptError, Exception, Object, Kernel, BasicObject]
moulon%