Issue #12198 has been updated by Sebastian Skalacki.


IMHO documentation on Hash#== is incorrect at the moment. It says:

> Equality¡½Two hashes are equal if they each contain the same number of keys and if each key-value pair is equal to (according to Object#==) the corresponding elements in the other hash.

Which is definitely inconsistent with what have been observed and described in this ticket.  Furthermore, two key-value pairs `[k1, v1]` and `[k2, v2]` are equal when `v1 == v2 && k1.eql?(k2) && k1.hash == k2.hash`:

~~~
a = Object.new   #=> #<Object:0x007f8d60eaf570>
b = Object.new   #=> #<Object:0x007f8d63713458>
def a.eql? _ ; true ; end   #=> :eql?
def b.eql? _ ; true ; end   #=> :eql?
a.eql?(b)   #=> true
{a => true} == {b => true}   #=> false
def a.hash ; 1 ; end   #=> :hash
def b.hash ; 1 ; end   #=> :hash
{a => true} == {b => true}   #=> true
~~~

The `k1.hash == k2.hash` condition is actually an implication of how `#eql?` is intended to work, the description of [`Object#hash`](http://ruby-doc.org/core-2.3.0/Object.html#method-i-hash) states that clearly:

> Generates a Fixnum hash value for this object. This function must have the property that a.eql?(b) implies a.hash == b.hash.

Unfortunately, the description of [`Object#eql?`](http://ruby-doc.org/core-2.3.0/Object.html#method-i-eql-3F) says something different:

> The eql? method returns true if obj and other refer to the same hash key. This is used by Hash to test members for equality. For objects of class Object, eql? is synonymous with ==. Subclasses normally continue this tradition by aliasing eql? to their overridden == method, but there are exceptions. Numeric types, for example, perform type conversion across ==, but not across eql?, so: (example follows)

Therefore, it suggests that `eql?` could be defined as:

~~~
def eql? other
  self.hash == other.hash
end
~~~

Which is not true:

~~~
a = Object.new   #=> #<Object:0x007fe18f819660>
b = Object.new   #=> #<Object:0x007fe18e3dcc60>
def a.hash ; 44 ; end   #=> :hash
def b.hash ; 44 ; end   #=> :hash
a.eql? b   #=> false
~~~

Finally, when it comes to [`Set#==`](http://ruby-doc.org/stdlib-2.3.0/libdoc/set/rdoc/Set.html#method-i-3D-3D) description:

> Returns true if two sets are equal. The equality of each couple of elements is defined according to Object#eql?.

It is correct, although I believe it could be improved too.  The need for `#rehash` (when introduced) could be mentioned and the fact that `#eql?` does not imply `#==` could be emphasised.  The latter is important because one could easily think that if `some_array_1 == some_array_2` then `some_array_1.to_set == some_array_2.to_set`, but this is not true.

To sum it all up:

== `Set#rehash` is required

I guess it's not controversial, or is it?  Can I make a pull request?

== `Hash#==` description is seriously wrong

It says:

> Equality¡½Two hashes are equal if they each contain the same number of keys and if each key-value pair is equal to (according to Object#==) the corresponding elements in the other hash.

It could say:

> Equality¡½Two hashes are equal if they each contain the same number of keys and if each key-value pair is equal to (keys according to Object.eql?, values according to Object#==) the corresponding elements in the other hash.

Moreover, some notice about the need for `Hash#rehash` is necessary.  I'm not sure whether it should be inserted here or in the "Hash Keys" section of the class description.

== `Object#eql?` description is wrong

It says:

> The eql? method returns true if obj and other refer to the same hash key. This is used by Hash to test members for equality. For objects of class Object, eql? is synonymous with ==. Subclasses normally continue this tradition by aliasing eql? to their overridden == method, but there are exceptions. Numeric types, for example, perform type conversion across ==, but not across eql?, so: (example follows)

It could say:

> The eql? is used by Hash to test members for equality. This method must have the property that a.eql?(b) implies a.hash == b.hash.  For objects of class Object, eql? is synonymous with ==. Subclasses normally continue this tradition by aliasing eql? to their overridden == method, but there are exceptions. Numeric types, for example, perform type conversion across ==, but not across eql?, so: (example follows)


== `Set#==` description is very good, but it could be improved as well

It says:
> Returns true if two sets are equal. The equality of each couple of elements is defined according to Object#eql?.
It could say:
> Returns true if two sets are equal. The equality of each couple of elements is defined according to Object#eql?.  Please note that this does not imply that these objects are equal according to Object#==, see Object#eql? for details.

Please improve English in the changes I've proposed.


----------------------------------------
Bug #12198: Hash#== sometimes returns false incorrectly
https://bugs.ruby-lang.org/issues/12198#change-57719

* Author: Sebastian Skalacki
* Status: Assigned
* Priority: Normal
* Assignee: Akinori MUSHA
* ruby -v: ruby 2.4.0dev (2016-03-11 trunk 54086) [x86_64-darwin14]
* Backport: 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN
----------------------------------------
Hi!

Sorry for lack of the accuracy in the bug title. I have some trouble with pinpointing the issue.

According to documentation, "two hashes are equal if they each contain the same number of keys and if each key-value pair is equal to (according to Object#==) the corresponding elements in the other hash." I was able to produce two hashes which satisfy this condition, however the method returns false. In other words, following happens:

~~~
e.class #=> Hash
r.class #=> Hash
e.size == r.size #=> true
e.each_pair.to_a == r.each_pair.to_a #=> true
e == r #=> false
~~~

That happens in Ruby 1.9.3, 2.3, 2.4 and probably in other versions as well. Pure Ruby, no gem could interfere.

Happy Easter ]:->

---Files--------------------------------
problem.rb (1.69 KB)


-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>