From: Josh Cheek Date: 2011-12-30T17:59:38+09:00 Subject: Re: Hash access --f46d043d66d9bc9dfa04b54b7358 Content-Type: text/plain; charset=ISO-8859-1 On Fri, Dec 30, 2011 at 1:41 AM, Yong Li wrote: > Hi Josh, > > Here is how Hash in Ruby works when it tries to determine if two keys are > equal: > * the #hash method on both objects are called to calculate their hash codes > * if their hash codes are not equal, they are not equal > * if their hash codes are equal, then #== is called to determine if > two objects are equal > > In your example, all three objects actually return the same hash > codes, so #== (instead of eql?) is used to check their equality. > > The "first Josh" and the "second Josh" are equal because their #== > (inherited from Object#==) simply calls #eql? which you have > overridden to make them equal. > > The "first Josh" is not equal to "Josh" because they are of different > classes, and User#== (inherited from Object#==) does not allow objects > of different classes to be equal. > > As a side note: you should always define #hash and #== together and > make sure whenever #== returns true #hash mush return the same number, > otherwise, using these objects as hash keys will break the hash > semantics. Also, avoid using mutable objects as hash keys unless their > #hash number is immutable. > > I hope this helps > > > On Fri, Dec 30, 2011 at 1:37 PM, Josh Cheek wrote: > > Hi, was playing around with an idea after reading the thread about > defining > > #hash. My understanding was that #hash gives a unique identifier, and > that > > #eql? allows the hash to determine whether the two objects are equal in > > terms of being the same hash key. So I wrote some code that should take > an > > equivalent instance, or a string for quick access. But it behaves in a > way > > that I completely don't understand. Hoping someone can help: > > > > > > User = Struct.new :name, :age, :identifier do > > def hash > > name.hash > > end > > > > def eql?(other) > > puts "#{name} was asked if they were equal to #{other.inspect}" > > (other == name) || (other.name == name && other.age == age) > > end > > end > > > > josh = User.new 'Josh', 28, 'first Josh' > > hash = {josh => josh} > > > > hash[josh] # => # > age=28, identifier="first Josh"> > > hash[User.new 'Josh', 28, 'second Josh'] # => # > age=28, identifier="first Josh"> > > hash['Josh'] # => nil > > > > # >> Josh was asked if they were equal to # > age=28, identifier="first Josh"> > > > > > > > > So I would have expected all three to go through eql? Instead, we see > that > > only the case where the key was the same object goes through. However, it > > identifies that the second Josh is the same key, without invoking > User#eql? > > How does it do this? > > > > And why does the string "Josh" not find the instance? > > > > This is all probably in my copy of the Pickaxe, but it's in Chicago and > I'm > > out of town :/ > > I see. The confusion for me was that the comparison goes in the other direction. (ie hash["Josh"] turns into "Josh".eql?(#) but I was thinking it would be #.eql?("Josh")). This becomes apparent if I change the log line to `puts "#{inspect} was asked if they were #eql? to #{other.inspect}"` I just didn't do that in the name of brevity, and it masked the discrepancy. So it's probably implemented something like this (ignoring nuances like collisions and default values) # expectation class Hash def [](key) potential_key, potential_value = at_hash key.hash return potential_value if potential_key.eql? key end end # actual class Hash def [](key) potential_key, potential_value = at_hash key.hash return potential_value if key.equal? potential_key return potential_value if key.eql? potential_key end end --f46d043d66d9bc9dfa04b54b7358--