Reputation: 13
I'm trying to correctly implement some custom classes in order to use them in a set. Custom class B contains an array instance variable that holds objects of class A. Here's a MWE:
#!/usr/bin/env ruby
require 'set'
class A
attr_reader :a
def initialize(a)
@a = a
end
def hash
@a.hash
end
def eql?(other)
@a == other.a
end
end
class B
attr_reader :instances
def initialize
@instances = Array.new
end
def add(i)
@instances.push(i)
end
def hash
@instances.hash
end
def eql?(other)
@instances == other.instances
#@instances.eql?(other.instances)
end
end
s = Set.new
b1 = B.new
b1.add(A.new(4))
b1.add(A.new(5))
b2 = B.new
b2.add(A.new(4))
b2.add(A.new(5))
s.add(b1)
s.add(b1)
s.add(b2)
puts s.size
The output is 2
, expected is 1
since b1
and b2
are objects constructed with the same values.
If I use eql?
instead of ==
in the implementation of eql?
in class B, then the output is correct. According to the definition of ==
in the ruby docs, shouldn't the use of ==
be correct here? Where is my error in understanding this?
Upvotes: 1
Views: 794
Reputation: 114188
TL;DR it's because:
Array#==
compares elements via ==
Array#eql?
compares elements via eql?
According to the definition of
==
in the ruby docs, shouldn't the use of==
be correct here?
If you compare the arrays via Array#==
the corresponding items will also be compared via ==
: (emphasis added)
Two arrays are equal if they contain the same number of elements and if each element is equal to (according to Object#==)
Example:
[A.new(4)] == [A.new(4)]
#=> false
it fails because the elements are not equal according to ==
:
A.new(4) == A.new(4)
#=> false
If I use
eql?
instead of==
in the implementation ofeql?
in classB
, then the output is correct.
Array#eql?
works, because it compares corresponding items via eql?
: (emphasis added)
Returns
true
if self and other are the same object, or are both arrays with the same content (according to Object#eql?).
Example:
[A.new(4)].eql? [A.new(4)]
#=> true
because of:
A.new(4).eql? A.new(4)
#=> true
In order to get ==
working, you have to implement A#==
:
class A
# ...
def eql?(other)
@a == other.a
end
alias_method :==, :eql?
end
A.new(4) == A.new(4) #=> true
[A.new(4)] == [A.new(4)] #=> true
Upvotes: 2