Loose Equality (was equal? versus eql? versus == versus === verus <=> )

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
for Object/Array/String/Hash/Set in order to understand things better
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?
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


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