Overriding == equality operator works only in one direction - ruby

Consider next example where I override == operator to return true constantly:
class Example
def ==(other)
return true
end
end
However, this only works in one direction:
exp = Example.new
puts exp == {} #=> true
puts {} == exp #=> false
Is there a way to force this equality method to work in the reverse direction?

It's not possible without changing other classes. Basically a == b equals a.==(b). So you need to override == operator for the second class if you want to make it work.

Is there a way to force this equality method to also work in the reverse direction?
No, but you can change Hash#== to recognize the special case of Example.
class Example
def ==(other)
return true
end
end
class Hash
alias_method :original_double_equals, :==
def ==(other)
case other
when Example
other == self
else
original_double_equals(other)
end
end
end
exp = Example.new
exp == {} # => true
{} == exp # => true
{} == {} # => true
{foo: 1} == {foo: 2} # => false
I wouldn't do this, if I were you.

No, it is not possible.
Ruby is an object-oriented language. The fundamental idea of OO is that you send messages to objects and the receiver object, and nothing but the receiver object gets to decide how to respond to that message.
If you send the message == to object a, passing object b as an argument, then a and a alone gets to decide how to respond.
If you send the message == to object b, passing object a as an argument, then b and b alone gets to decide how to respond.
The only way that this is guaranteed to give the same response, is if a and b cooperate and agree to give the same response.
It would theoretically be possible to design a language in such a way that a == b is not a message send to either a or b, but rather a message send to some third "context object". IOW, a == b is not interpreted as a.==(b) or b.==(a) but rather some_context_object.==(a, b) (for example, the context object could just be yourself, i.e. self.==(a, b)). In that case, the context object has access to the public API of both a and b and could take steps to ensure that == is commutative.
But in general, and by the fundamental nature of OO, messages are non-commutative, and the receiver object is special.

Related

Is the order of the equality operator important in Ruby?

I have used the bcrypt library in my Ruby program. I noticed that the order of the equality operator seems to be important. Depending on which variable is left or right of the '==' I get a different result.
Here is an example program:
require 'bcrypt'
my_pw = "pw1"
puts "This is my unhashed password: #{my_pw}"
hashed_pw = BCrypt::Password.create(my_pw)
puts "This is my hashed password: #{hashed_pw}"
20.times{print"-"}
puts
puts "my_pw == hashed_pw equals:"
if (my_pw == hashed_pw)
puts "TRUE"
else
puts "FALSE"
end
puts "hashed_pw == my_pw equals:"
if (hashed_pw == my_pw)
puts "TRUE"
else
puts "FALSE"
end
Regards
schande
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 if object has the same length and content; as self; 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.
This doesn't really have anything to do with equality. This is just fundamentals of Object-Oriented Programming.
In OOP, all computation is done by objects sending messages to other objects. And one of the fundamental properties of OOP is that the receiver object and only the receiver object decides how to respond to this message. This is how encapsulation, data hiding, and abstraction are achieved in OOP.
So, if you send the message m to object a passing b as the argument, then a gets to decide how to interpret this message. If you send the message m to object b passing a as the argument, then it is b which gets to decide how to interpret this message. There is no built-in mechanism that would guarantee that a and b interpret this message the same. Only if the two objects decide to coordinate with each other, will the response actually be the same.
If you think about, it would be very weird if 2 - 3 and 3 - 2 had the same result.
That is exactly what is happening here: In the first example, you are sending the message == to my_pw, passing hashed_pw as the argument. my_pw is an instance of the String class, thus the == message will be dispatched to the String#== method. This method knows how to compare the receiver object to another String. It does, however, not know how to compare the receiver to a BCrypt::Password, which is what the class of hashed_pw is.
And if you think about it, that makes sense: BCrypt::Password is a third-party class from outside of Ruby, how could a built-in Ruby class know about something that didn't even exist at the time the String class was implemented?
In your second example, on the other hand, you are sending the message == to hashed_pw, passing my_pw as the argument. This message gets dispatched to the BCrypt::Password#== method, which does know how to compare the receiver with a String:
Method: BCrypt::Password#==
Defined in: lib/bcrypt/password.rb
#==(secret) ⇒ Object
Also known as: is_password?
Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
Actually, the problem in this particular case is even more subtle than it may at first appear.
I wrote above that String#== doesn't know what to do with a BCrypt::Password as an argument, because it only knows how to compare Strings. Well, actually BCrypt::Password inherits from String, meaning that a BCrypt::Password IS-A String, so String#== should know how to handle it!
But think about what String#== does:
string == object → true or false
Returns true if object has the same length and content; as self; false otherwise […]
Think about this: "returns true if object has the same length and content". For a hash, this will practically never be true. self will be something like 'P#$$w0rd!' and object will be something like '$2y$12$bxWYpd83lWyIr.dF62eO7.gp4ldf2hMxDofXPwdDZsnc2bCE7hN9q', so clearly, they are neither the same length nor the same content. And object cannot possibly be the same content because the whole point of a cryptographically secure password hash is that you cannot reverse it. So, even if object somehow wanted to present itself as the original password, it couldn't do it.
The only way this would work, is if String and BCrypt::Password could somehow "work together" to figure out equality.
Now, if we look close at the documentation of String#==, there is actually a way to make this work:
If object is not an instance of String but responds to to_str, then the two strings are compared using object.==.
So, if the author of BCrypt::Password had made a different design decision, then it would work:
Do not let BCrypt::Password inherit from String.
Implement BCrypt::Password#to_str. This will actually allow BCrypt::Password to be used practically interchangeably with String because any method that accepts Strings should also accept objects that respond to to_str.
Now, as per the documentation of String#==, if you write my_pw == hashed_pw, the following happens:
String#== notices that hashed_pw is not a String.
String#== notices that hashed_pw does respond to to_str.
Therefore, it will send the message object == self (which in our case is equivalent to hashed_pw == my_pw), which means we are now in the second scenario from your question, which works just fine.
Here's an example of how that would work:
class Pwd
def initialize(s)
#s = s.downcase.freeze
end
def to_str
p __callee__
#s.dup.freeze
end
def ==(other)
p __callee__, other
#s == other.downcase
end
end
p = Pwd.new('HELLO')
s = 'hello'
p == s
# :==
# "hello"
#=> true
s == p
# :==
# "hello"
#=> true
As you can see, we are getting the results we are expecting, and Pwd#== gets called both times. Also, to_str never gets called, it only gets inspected by String#==.
So, it turns out that ironically, the problem is not so much that String#== doesn't know how to deal with BCrypt::Password objects, but actually the problem is that it does know how to deal with them as generic Strings. If they weren't Strings but merely responded to to_str, then String#== would actually know to ask them for help.
Numeric objects in Ruby have a whole coercion protocol to make sure arithmetic operations between different "number-like" operand types are supported even for third-party numerics libraries.
The expressions would be equivalent if both operands were, for instance, of type String. In your case, one operand is a String and the other one is a BCrypt::Password. Therefore my_pw == hashed_pw invokes the equality method defined in the String class, while hashed_pw == my_pw invokes the one defined in BCrypt::Password.
I have never worked with BCrypt::Password, but would expect that you get false for the former and true for the latter.
In Ruby you can override the equality method for a given class or instance:
class Test
define_method(:==) do |_other|
true
end
end
Test.new == 'foo' # => true
Test.new == nil # => true
Test.new == 42 # => true
'foo' == Test.new # => false
nil == Test.new # => false
42 == Test.new # => true
Generally speaking, it's considered bad practice to override equality without making it symetric, but you sometimes see it in the wild.

Ruby Set with custom class to equal basic strings

I want to be able to find a custom class in my set given just a string. Like so:
require 'set'
Rank = Struct.new(:name, keyword_init: true) {
def hash
name.hash
end
def eql?(other)
hash == other.hash
end
def ==(other)
hash == other.hash
end
}
one = Rank.new(name: "one")
two = Rank.new(name: "two")
set = Set[one, two]
but while one == "one" and one.eql?("one") are both true, set.include?("one") is still false. what am i missing?
thanks!
Set is built upon Hash, and Hash considers two objects the same if:
[...] their hash value is identical and the two objects are eql? to each other.
What you are missing is that eql? isn't necessarily commutative. Making Rank#eql? recognize strings doesn't change the way String#eql? works:
one.eql?('one') #=> true
'one'.eql?(one) #=> false
Therefore it depends on which object is the hash key and which is the argument to include?:
Set['one'].include?(one) #=> true
Set[one].include?('one') #=> false
In order to make two objects a and b interchangeable hash keys, 3 conditions have to be met:
a.hash == b.hash
a.eql?(b) == true
b.eql?(a) == true
But don't try to modify String#eql? – fiddling with Ruby's core classes isn't recommended and monkey-patching probably won't work anyway because Ruby usually calls the C methods directly for performance reasons.
In fact, making both hash and eql? mimic name doesn't seem like a good idea in the first place. It makes the object's identity ambiguous which can lead to very strange behavior and hard to find bugs:
h = { one => 1, 'one' => 1 }
#=> {#<struct Rank name="one">=>1, "one"=>1}
# vs
h = { 'one' => 1, one => 1 }
#=> {"one"=>1}
what am i missing?
What you are missing is that "one" isn't in your set. one is in your set, but "one" isn't.
Therefore, the answer Ruby is giving you is perfectly correct.
All that you have done with your implementation of Rank is that any two ranks with the same name are considered to be the same by a Hash, Set, or Array#uniq. But, a Rank is not the same as a String.
If you want to be able to have a set-like data structure where you can look up things by one of their attributes, you will have to write it yourself.
Something like (untested):
class RankSet < Set
def [](*args)
super(*args.map(&:name))
end
def each
return enum_for(__callee__) unless block_given?
super {|e| yield e.name }
end
end
might get you started.
Or, instead of writing your own set, you can just use the fact that any arbitrary rank with the right name can be used for lookup:
set.include?(Rank.new(name: "one"))
#=> true
# even though it is a *different* `Rank` object

Safe navigation operator (&.) for nil

As Ruby 2.3 introduces the Safe navigation operator(&.), a.k.a lonely operator, the behavior on nil object seems odd.
nil.nil? # => true
nil&.nil? # => nil
Is that designed to behave like this way? Or some edge case that slipped away when adding the lonely operator?
foo&.bar is shorthand for foo && foo.bar, so what would you expect the result of the expression nil && nil.nil? to be?
This is because nil&.nil? is shorthand for nil && nil.nil?. This would evaluate to nil && true, which is then nil.
(nil && x).nil? always evaluates to true, for any value of x.
While the syntax has power, this specific case has some potential to become a 'gotcha' for a developer:
(stuff&.things).nil? => This produces true if stuff doesn't exist, or stuff.things returns nil.
vs. the below case:
stuff&.things&.nil? => This produces nil in every case except the case where stuff.things returns something other than nil, in which case it would return false.
Because of the difficulty in normal boolean logic of differentiating between false and nil, it is unlikely that this would make sense in normal logic.
safe navigation operator: tells Ruby to only call the next method if the receiver isn’t nil. Otherwise, the expression returns nil.
class Roster
attr_accessor :players
end
class Player
attr_accessor :name, :position
def initialize(name, position)
#name = name
#position = position
end
end
With these two objects, we can create a roster for a 2-on-2 women’s basketball tournament:
moore = Player.new("Maya Moore", "Forward")
taurasi = Player.new("Diana Taurasi", "Guard")
tourney_roster1 = Roster.new
tourney_roster1.players = [moore, taurasi]
If we want to know the forward for our 2-on-2 team, we might find the name this way:
if tourney_roster1.players.first.position == "Forward"
puts "Forward: #{tourney_roster1.players.first.name}"
end
But what if our opposing roster isn’t set correctly?
tourney_roster2 = Roster.new
if tourney_roster2.players.first.position == "Forward"
puts "Forward: #{tourney_roster1.players.first.name}"
end
tourney_roster2 hasn’t yet been set with any players. The preceding code will raise a NoMethodError because tourney_roster2.players returns nil. We can add conditional statements to avoid this, but it makes our if statement verbose and unclear:
if tourney_roster2.players &&
tourney_roster2.players.first &&
tourney_roster2.players.first.position == "Forward"
Instead, we can use the safe navigation operator to avoid the NoMethodError:
if tourney_roster2.players&.first&.position == "Forward"
puts "Forward: #{tourney_roster1.players.first.name}"
end
Some legitimate use cases: The safe navigation operator comes in handy when working with multiple objects, as shown here, and when chaining methods together.

a == b returns true, a.attribute == b.attribute returns false; ruby

I'm testing this line of a controller:
#channel.update_attribute(:active, true)
expect(channel.active).to be_true fails (my variable is named 'channel')
expect(assigns[:channel].active).to be_true passes
assigns[:channel] == channel
=> true
assigns[:channel].active == channel.active
=> false
assigns[:channel].active == channel.reload.active
=> true
I don't understand why the comparison operator says that assigns[:channel] == channel, but that their "active" attribute is different.
I suppose a channel is an ActiveRecord object and it's the same as doing:
channel1 = Channel.find(1)
channel2 = Channel.find(1)
So channel1 == channel2 because their identity is the same. (they are both channels and have the same id)
But if you update one of them, the other will be stale.
channel1.active = "different value"
channel1.save
channel1.active != channel2.active
Doing channel2.reload will fetch the attributes from the database and now
channel1.active == channel2.active
http://api.rubyonrails.org/classes/ActiveRecord/Core.html#method-i-3D-3D
Because equality for ActiveRecord models is based on their ID, not every attribute. So it is the same model, they just have a different settings for their "active" attribute. That's why it works after you reload.
I think it might work like you expect if you change the first line:
#channel.update(active: true)
I'm not sure what exactly is happening in your specific situation, but the general situation is not at all strange:
class Foo
attr_reader :bar
def initialize
#bar = Bar.new
end
def ==(*) true end
class Bar
def ==(*) false end
end
end
a = Foo.new
b = Foo.new
a == b
# => true
a.bar == b.bar
# => false
This is just a consequence of object-oriented encapsulation. It is up to a to decide whether or not it thinks it is equal to b, and it is up to a.bar to decide whether or not it thinks it is equal to b.bar, and there is nothing to enforce that the two have to agree.
In your case, there seems to be some caching consistency issue. You seem to have two different objects that both represent the same database record, and one of them has stale data.

Why can procs be invoked with === in ruby 1.9?

This article mentions 4 ways to invoke procs in ruby 1.9, and === is one of them. I don't understand why this would be done this way at all. Does it have any relationship to the normal meaning of === (asking if the two objects are the same object)?
irb(main):010:0> f =-> n {[:hello, n]}
=> #
irb(main):011:0> f.call(:hello)
=> [:hello, :hello]
irb(main):012:0> f === :hello
=> [:hello, :hello]
irb(main):013:0> Object.new === Object.new
=> false
irb(main):014:0> f === f
=> [:hello, #]
Note that === in Ruby is NOT about equality, unlike JavaScript. It is specifically used for case expressions:
case cats.length
when 42 # Uses 42 === cats.length
puts :uh
when /cool/i # Uses /cool/i === cats.length
puts :oh
when ->(n){ n.odd? || n/3==6 } # Passes cats.length to the proc
puts :my
end
This is what the docs have to say:
It is to allow a proc object to be a target of when clause in the
case statement.
This is a, perhaps contrived, example:
even = proc { |x| x % 2 == 0 }
n = 3
case n
when even
puts "even!"
else
puts "odd!"
end
It works because the case/when is basically executed like this:
if even === n
puts "even!"
else
puts "odd!"
end
The case/when checks which branch to execute by calling === on the arguments to when clauses, picking the first that returns a truthy value.
Despite its similarity to the equality operator (==) it not a stronger or weaker form of it. I try to think of the === operator as the "belongs to" operator. Class defines it so that you can check if an object belongs to the class (i.e. is an instance of the class or a subclass of the class), Range defines it as to check if the argument belongs to the range (i.e. is included in the range), and so on. This doesn't really make the Proc case make more sense, but think of it as a tool for making your own belongs to operators, like my example above; I defined an object that can determine if something belongs to the set of even numbers.
This feature is useful in case construction, when you need to calculate something at the comparing.
is_odd =-> n { n%2 != 0 }
is_even =-> n { n%2 == 0 }
case 5
when is_even
puts "the number is even"
when is_odd
puts "the number is odd"
end
=> the number is odd
Does it have any relationship to the normal meaning of === (asking if the two objects are the same object)?
Actually, that's a common misconception about === in Ruby. It's actually not strictly for Object#object_id comparison (although that is its behavior in many common invocations). In Ruby, === is case subsumption.
Here's the description of === from Object: "Case Equality -- For class Object, effectively the same
as calling #==, but typically overridden by descendants
to provide meaningful semantics in case statements."
Sadly, even though it is comprised of three =, it doesn't have anything even remotely to do with equality :-D

Resources