What's the right way to implement equality in ruby
To simplify comparison operators for objects with more than one state variable, create a method that returns all of the object's state as an array. Then just compare the two states:
class Thing
def initialize(a, b, c)
@a = a
@b = b
@c = c
end
def ==(o)
o.class == self.class && o.state == state
end
protected
def state
[@a, @b, @c]
end
end
p Thing.new(1, 2, 3) == Thing.new(1, 2, 3) # => true
p Thing.new(1, 2, 3) == Thing.new(1, 2, 4) # => false
Also, if you want instances of your class to be usable as a hash key, then add:
alias_method :eql?, :==
def hash
state.hash
end
These need to be public.
What's the difference between equal?, eql?, ===, and ==?
I'm going to heavily quote the Object documentation here, because I think it has some great explanations. I encourage you to read it, and also the documentation for these methods as they're overridden in other classes, like String.
Side note: if you want to try these out for yourself on different objects, use something like this:
class Object
def all_equals(o)
ops = [:==, :===, :eql?, :equal?]
Hash[ops.map(&:to_s).zip(ops.map {|s| send(s, o) })]
end
end
"a".all_equals "a" # => {"=="=>true, "==="=>true, "eql?"=>true, "equal?"=>false}
==
— generic "equality"
At the Object level,
==
returns true only ifobj
andother
are the same object. Typically, this method is overridden in descendant classes to provide class-specific meaning.
This is the most common comparison, and thus the most fundamental place where you (as the author of a class) get to decide if two objects are "equal" or not.
===
— case equality
For class Object, effectively the same as calling
#==
, but typically overridden by descendants to provide meaningful semantics in case statements.
This is incredibly useful. Examples of things which have interesting ===
implementations:
- Range
- Regex
- Proc (in Ruby 1.9)
So you can do things like:
case some_object
when /a regex/
# The regex matches
when 2..4
# some_object is in the range 2..4
when lambda {|x| some_crazy_custom_predicate }
# the lambda returned true
end
See my answer here for a neat example of how case
+Regex
can make code a lot cleaner. And of course, by providing your own ===
implementation, you can get custom case
semantics.
eql?
— Hash
equality
The
eql?
method returns true ifobj
andother
refer to the same hash key. This is used byHash
to test members for equality. For objects of classObject
,eql?
is synonymous with==
. Subclasses normally continue this tradition by aliasingeql?
to their overridden==
method, but there are exceptions.Numeric
types, for example, perform type conversion across==
, but not acrosseql?
, so:1 == 1.0 #=> true
1.eql? 1.0 #=> false
So you're free to override this for your own uses, or you can override ==
and use alias :eql? :==
so the two methods behave the same way.
equal?
— identity comparison
Unlike
==
, theequal?
method should never be overridden by subclasses: it is used to determine object identity (that is,a.equal?(b)
iffa
is the same object asb
).
This is effectively pointer comparison.
What does the Ruby `uniq` method use for equality checking?
It compares values using their hash and eql? methods for efficiency.
https://ruby-doc.org/core-2.5.0/Array.html#method-i-uniq-3F
So you should override eql?
(that is ==
) and hash
UPDATE:
I cannot explain fully why is that, but overriding hash
and ==
doesn't work. I guess it's cause by the way uniq
is implemented in C:
From: array.c (C Method):
Owner: Array
Visibility: public
Number of lines: 20
static VALUE
rb_ary_uniq(VALUE ary)
{
VALUE hash, uniq;
if (RARRAY_LEN(ary) <= 1)
return rb_ary_dup(ary);
if (rb_block_given_p()) {
hash = ary_make_hash_by(ary);
uniq = rb_hash_values(hash);
}
else {
hash = ary_make_hash(ary);
uniq = rb_hash_values(hash);
}
RBASIC_SET_CLASS(uniq, rb_obj_class(ary));
ary_recycle_hash(hash);
return uniq;
}
You can bypass that by using a block version of uniq:
> [Foo.new(1,2), Foo.new(1,2), Foo.new(2,3)].uniq{|f| [f.a, f.b]}
=> [#<Foo:0x0000562e48937cc8 @a=1, @b=2>, #<Foo:0x0000562e48937c78 @a=2, @b=3>]
Or use Struct
instead:
F = Struct.new(:a, :b)
[F.new(1,2), F.new(1,2), F.new(2,3)].uniq
# => [#<struct F a=1, b=2>, #<struct F a=2, b=3>]
UPDATE2:
Actually in terms of overriding it's not the same if you override ==
or eql?
. When I overriden eql?
It worked as intended:
class Foo
attr_accessor :a, :b
def initialize(a, b)
@a = a
@b = b
end
def eql?(other)
(@a == other.a && @b == other.b)
end
def hash
[a, b].hash
end
def to_s
"#{@a}: #{@b}"
end
end
a = [
Foo.new(1, 1),
Foo.new(1, 2),
Foo.new(2, 1),
Foo.new(2, 2),
Foo.new(2, 2)
]
a.uniq
#=> [#<Foo:0x0000562e483bff70 @a=1, @b=1>,
#<Foo:0x0000562e483bff48 @a=1, @b=2>,
#<Foo:0x0000562e483bff20 @a=2, @b=1>,
#<Foo:0x0000562e483bfef8 @a=2, @b=2>]
Is == in Ruby always value equality?
In Ruby, ==
can be overloaded, so it could do anything the designer of the class you're comparing wants it to do. In that respect, it's very similar to Java's equals()
method.
The convention is for ==
to do value comparison, and most classes follow that convention, String
included. So you're right, using ==
for comparing strings will do the expected thing.
The convention is for equal?
to do reference comparison, so your test a.object_id == b.object_id
could also be written a.equal?(b)
. (The equal?
method could be defined to do something nonstandard, but then again, so can object_id
!)
(Side note: when you find yourself comparing strings in Ruby, you often should have been using symbols instead.)
Ruby. How is the case equality method implemented in the class Class?
We can further simplify your question I think.
I believe you are asking is why
String == String # true
But
String === String # false
I think it's semi consistent by Ruby. the === equality asks if right side is a member of the left side.
So
Class === String
Is true since String is a member of Class. And indeed String is not a member of String.
What I do find weird though is that
5 === 5 # returns true
Imo it should return false to be consistent with String === String returning false, but for primitives Ruby has this quirk, probably so it works well with case statements.
What methods gets called when I try to compare custom object with string?
You need to override your class' ==
method.
class MyClass
def ==(other)
# custom equal comparison logic here
# if you just need string comparison
to_s == other
end
end
=== vs. == in Ruby
The two really have nothing to do with each other. In particular, #==
is the equality operator and #===
has absolutely nothing to with equality. Personally, I find it rather unfortunate that #===
looks so similar to #==
, uses the equals sign and is often called the case equality operator, triple equals operator or threequals operator when it really has nothing to do with equality.
I call #===
the case subsumption operator (it's the best I could come up with, I'm open to suggestions, especially from native English speakers).
The best way to describe a === b
is "if I have a drawer labeled a
, does it make sense to put b
in it?"
So, for example, Module#===
tests whether b.is_a?(a)
. If you have Integer === 2
, does it make sense to put 2
in a box labeled Integer
? Yes, it does. What about Integer === 'hello'
? Obviously not.
Another example is Regexp#===
. It tests for a match. Does it make sense to put 'hello'
in a box labeled /el+/
? Yes, it does.
For collections such as ranges, Range#===
is defined as a membership test: it makes sense to put an element in a box labeled with a collection if that element is in the collection.
So, that's what #===
does: it tests whether the argument can be subsumed under the receiver.
What does that have to with case
expressions? Simple:
case foo
when bar
baz
end
is the same as
if bar === foo
baz
end
What does the === operator do in Ruby?
Just like with every other method in Ruby (or actually pretty much any object-oriented language),
a === b
means whatever the author of a
's class wants it to mean.
However, if you don't want to confuse the heck out of your colleagues, the convention is that ===
is the case subsumption operator. Basically, it's a boolean operator which asks the question "If I have a drawer labelled a
would it make sense to put b
in that drawer?"
An alternative formulation is "If a
described a set, would b
be a member of that set?"
For example:
(1..5) === 3 # => true
(1..5) === 6 # => false
Integer === 42 # => true
Integer === 'fourtytwo' # => false
/ell/ === 'Hello' # => true
/ell/ === 'Foobar' # => false
The main usage for the ===
operator is in case
expressions, since
case foo
when bar
baz
when quux
flurb
else
blarf
end
gets translated to something (roughly) like
_temp = foo
if bar === _temp
baz
elsif quux === _temp
flurb
else
blarf
end
Note that if you want to search for this operator, it is usually called the triple equals operator or threequals operator or case equality operator. I really dislike those names, because this operator has absolutely nothing whatsoever to do with equality.
In particular, one would expect equality to be symmetric: if a
is equal to b
, then b
better be also equal to a
. Also, one would expect equality to be transitive: if a == b
and b == c
, then a == c
. While there is no way to actually guarantee that in a single-dispatch language like Ruby, you should at least make an effort to preserve this property (for example, by following the coerce
protocol).
However, for ===
there is no expectation of either symmetry or transitivity. In fact, it is very much by design not symmetric. That's why I don't like calling it anything that even remotely resembles equality. It's also why I think, it should have been called something else like ~~~
or whatever.
Is the order of the equality operator important in Ruby?
Yes, there is a difference.
my_pw == hashed_pw
calls the ==
method on the my_pw
string and passes hashed_pw
as an argument. That means you are using the String#==
method. From the docs of String#==
:
string == object → true or false
Returns
true
ifobject
has the same length and content; asself
;false
otherwise
Whereas hashed_pw == my_pw
calls the ==
method on an instance of BCrypt::Password
and passes my_pw
as an argument. From the docs of BCrypt::Password#==
:
#==(secret) ⇒ Object
Compares a potential secret against the hash. Returns
true
if the secret is the original secret,false
otherwise.
Related Topics
Actioncontroller::Routingerror (No Route Matches [Put] ) for Ajax Call
Optimization for Finding Perfect-Square Algorithm
Instance_Eval Block Not Supplied
How to Fix a Deadlock Caused by Open
Why Doesn't 'User Installation Directory' Match with the Ruby Version
Troubleshooting Ssl Certificates, Ruby, MAC Os X Yosemite
What Is the &: of &:Afunction Doing
How to Find Where Gem Files Are Installed
How to Step Out of a Loop with Ruby Pry
How to Find the Path a Ruby Gem Is Installed at (I.E. Gem.Lib_Path C.F. Gem.Bin_Path)
What Does the "Yield" Keyword Do in Ruby