I went a little different route, but that was indeed the problem. I just
defined == in my class, which I guess was comparing the class of the object
instead of the object_id. I did:
def == obj
self.equal?(obj)
end
If you have time, or if someone else does, is there a quick explanation of why
this happened in this instance,
If you just defined == the way you presented above then the issue should
not have occurred because equal? tests for identity. It's difficult to
comment on fragments when the whole code is not presented.
but not with similar classes that function
fine with essentially duplicate methods of adding to and deleting from
arrays?
I am not sure what you mean by this. Can you elaborate?
Hopefully I can avoid future problems if I understand what's
happening (I'm a hobby programmer, so I lack some technical expertise).
There are some concepts you should keep in mind that have some
similarities and will typically wreck havoc on your code when confused.
Both define relations (in the mathematical sense) on objects.
First, there is "identity". Two objects are identical when they are
actually just one, i.e. the same instance.
Then, there is equivalence. Equivalence is defined per class. For
example two strings containing the same sequence of characters are
equivalent. Sometimes only identical instances are equivalent.
Now, these different concepts are implemented in Ruby via different methods:
eql? and == implement equivalence
equal? implements identity
Most containers (an Array is a container) use equivalence, namely
implemented via eql? to test whether some objects match (e.g. for
deletion), because it is the more flexible and more useful concept.
(Think of an Array of Strings and you want to delete one of them with a
certain character sequence, you would want to provide a string with that
sequence as template and not the exact same object in the array - which
you might not know beforehand.)
Now, there is a slight twist: since for some algorithms it's not
efficient to compare something against all elements in the container
(for example, Array#uniq would have to compare every element of the
array with every other element which is O(n*n), i.e. if you double the
elements in the Array you quadruple the number of comparisons
necessary). In those cases (unfortunately they are not all documented)
typically a Hash is used behind the scenes. For objects to work
properly as Hash key methods eql? *and* hash need to be implemented
properly.
Consequence is, that you should always implement eql? and hash (and also
== for consistency) reasons *if* you plan to use instances of your class
in these circumstances *and* want to define equivalence different than
via identity (which happens to be the default implementation in class
Object). Typically you will choose some fields for this and you must
also make sure that equivalent instances yield the same (!) hash code.
Normally you do that by applying some math operation (binary XOR is
frequently used, because it's fast and guarantees that all values used
influence the result) on the hash values of those members that you
determine as key elements for equivalence.
The easiest way to do that is by using Struct, because that will
generate a class with all the necessary methods. Example:
# name and age are key for Foo
Foo = Struct.new :name, :age do
attr_accessor :unimportant_other_attribute
end
irb(main):007:0> f1 = Foo.new("a", 10)
=> #<struct Foo name="a", age=10>
irb(main):008:0> f2 = Foo.new("a", 10)
=> #<struct Foo name="a", age=10>
irb(main):009:0> f1.hash
=> -2186440
irb(main):010:0> f2.hash
=> -2186440
irb(main):011:0> f1.eql? f2
=> true
irb(main):012:0> f1 == f2
=> true
irb(main):013:0> f1.equal? f2
=> false
irb(main):014:0> f1.unimportant_other_attribute = "bar"
=> "bar"
irb(main):015:0> f1.eql? f2
=> true
irb(main):016:0> f1.name = "hello"
=> "hello"
irb(main):017:0> f1.eql? f2
=> false
There is another thing you should be aware: numbers in Ruby actually
implement *two* different equivalence relations:
irb(main):018:0> 1 == 1.0
=> true
irb(main):019:0> 1.eql? 1.0
=> false
irb(main):020:0> 1.hash
=> 3
irb(main):021:0> 1.0.hash
=> 233071
But most classes treat == and eql? synonym.
Next week we'll dive into ordering and operator <=>.
)
Kind regards
robert