> Chad Perrin wrote: > On Wed, Oct 17, 2007 at 09:12:26PM +0900, Rick DeNatale wrote: >> 1) Using hashs as keys in another hash is not a common use case. >> I'm a >> littlehard-pressed to think of why I'd want to, although I'm famous >> for lack of imagination. >> 2) Because of the requirement that obj1.eql? obj2 => obj1.hash == >> obj2.hash, implementing Hash#hash requires iterating over the keys >> and >> values and would be fairly expensive and make accessing a hash with >> hash keys by key impractical. > > foo.eql? still seems a little inconsistent (and surprising) to me. > Essentially, it's == except when comparing hashes, at which point it > suddenly becomes foo.equal?. This isn't really true. I just spent some time looking at the 1.8 sources for Object/Array/String/Hash/Set in order to understand things better and found some interesting insights into eql? vs == that I haven't seen mentioned before. Here is how I would characterize equal?, eql? and == equal? object identity test eql? strict equality test (defaults to identity) == loose equality test (defaults to identity) Strict equality for Object, Array, String, and Hash means that the class of the objects are identical. I mention this because that is not the case with loose equality. Array and String define 'strict equality' based on their contents. The containers don't have have to be identical but the contents must follow the 'strict equality' rule. For arrays, this means considering x.eql? (y) for all the corresponding elements. For strings, it just means a byte comparison of the contents. Object and Hash define 'strict equality' as 'object identity'. The other messages in the thread give some reasons why this is a reasonable definition for Hash (i.e. why the contents are ignored). The more interesting concept is 'loose equality' defined by ==. What I didn't know and what isn't clear from the documentation is that Array, String, and Hash all implement a coercion 'protocol' for #==. If the object on the right hand side responds to to_ary, to_str, or to_hash respectively, then the receiver and argument are reversed and #== is called again. str == str_pretender becomes str_pretender == str at which point the implementation of == as provided by the 'pretender' comes into play. This little trick means that can you construct your own classes that pretend to be arrays, strings, or hashes and == will commute correctly with the standard classes as long as you take care to define to_ary/to_str/to_hash and ==. Nice. In addition, it is important to note that while Hash#== uses #eql? to compare *keys* it uses #== to compare values: h1 = {1 => 2} h2 = {1 => 2.0} h3 = {1.0 => 2.0} h1.eql?(h2) # false (identity test) h1 == h2 # true (2 == 2.0 is true) h1 == h3 # false (1.eql?(1.0) is false) Because #== is used on the values, the coercion protocol I mentioned above will come into play when comparing hash contents. I didn't look at <=> as closely but it looks like Array#<=> will happily convert the right hand side via #to_ary if available. String#<=> looks for to_str and then applies the coercion protocol as with == taking care to reverse the results. Objects and Hashes don't have a natural ordering so <=> isn't defined. Set Set is a bit strange. Set#eql? depends on Hash#eql? on an internal hash but it isn't possible for two sets to share the same internal hash and since Hash#eql? is based on object identity it isn't possible for Set#eql? to ever return true, which means that 'strict equality' for sets is the same as object identity. If the definition of Hash#eql? changed, then Set#eql? would follow. Set#== doesn't implement a coercion protocol and sets aren't ordered so no Set#<=>. Gary Wright