Quoting gsinclair@xxxxxxxxxxxxxxx, on Tue, Mar 15, 2005 at 08:26:24AM +1100:
> On Monday, March 14, 2005, 6:19:24 PM, Yukihiro wrote:
>
> > |Would this be useful to others? What would a good example be?
>
> > Might be. Would someone in rdoc reference maintainers merge this if
> > he feels this one valuable?
>
> > matz.
>
> I think it's valuable and would like someone to commit it, once an
> example has been found. I think a good example is
>
> class Details
> attr_accessor :name, :age
> def ==(other)
> @name = other.name and @age = other.age
> end
> def hash
> @name.to_s + "," + @age.to_s
The "," is just noise, and an extra string, isn't it? I think this
could be done as:
@name.hash ^ @age.hash
I base that what Tanaka did in resolv.rb, but I just looked at what
Array does:
h = self.length
self.each { |i|
h = (h << 1) | (h<0 ? 1 : 0)
n = i.hash
h ^= n
}
(pseudo code xlated from C).
I assume the shifting business is so that [1,2].hash != [2,1].hash,
which your "," would have the effect of, as well.
> A good rule of thumb (which should be included in the documentation):
> if you define #== in a class, you should also define #hash. It's
> "data" objects that typically need these, because they are the kinds
> of things that will be stored in hashes.
Thats an interesting way of looking at it, it sounds right.
Perhaps Object#== should have a note to that effect?
Perhaps a better/another way to demonstrate would be
class Name
attr_reader :first, :last
def initialize(first, last)
@first = first.to_str, @last = last.to_str
end
def eql?(other)
first.downcase == other.first.downcase && last == other.last.downcase
end
def ==(other)
eql?(other)
end
def hash
[first.downcase, last.downcase].hash
end
end
This has the property that a.eql? b => true implies a.hash == b.hash, as
Object#hash documents.
Also it has the #==, which is your suggested warning that a #hash should
be defined.
Also, it ensures that
Name.new('Joe', 'smith').hash != Name.new('smith','joe').hash
Is that important? It's ok for two object that are not #eql? to hash
together, Hash will further distinguish them with #eql?, that's why it
needs it.
Maybe an example of the pitfalls of classes that say they are == and
eql? but hash to different values would be informative:
irb(main):001:0> class Foo; def hash; 1; end; def eql?(o); true; end;
end
=> nil
irb(main):002:0> class Bar; def hash; 2; end; def eql?(o); true; end;
end
=> nil
irb(main):003:0> h = {}
=> {}
irb(main):004:0> h[Foo.new] = nil
=> nil
irb(main):005:0> h
=> {#<Foo:0x34d2d8>=>nil}
irb(main):006:0> h[Foo.new] = 2
=> 2
irb(main):007:0> h
=> {#<Foo:0x34d2d8>=>2}
irb(main):008:0> h[Bar.new] = 4
=> 4
irb(main):009:0> h
=> {#<Foo:0x34d2d8>=>2, #<Bar:0x336b8c>=>4}
irb(main):010:0>
I'm looking at the docs for Object#==, and they have a good description
of this.
The difference between == and eql? is subtle. Perhaps a decent way of
thinking of the difference is whether you think that they should be the
same keys when used in a Hash. If so, eql? must be true.
Cheers,
Sam
|