Ruby operator overloading
I believe the answer to "ruby operator overloading question" addresses both your points by using is_a?
and coerce
.
With regards to your first point. The normal approach in Ruby is to use respond_to?
where possible, rather than checking for type. If for some reason you specifically need to check for type, then using is_a?
is the correct way.
list of ruby operators that can be overridden/implemented
Here's a table of the Ruby operators.
The ones that are methods and overloadable are:
[ ] [ ]= Element reference, element set
** Exponentiation
! ~ + - Not, complement, unary plus and minus (method names for the last two are +@ and -@)
* / % Multiply, divide, and modulo
+ - Plus and minus
>> << Right and left shift
& Bitwise `and'
^ | Bitwise exclusive `or' and regular `or'
<= < > >= Comparison operators
<=> == === != =~ !~ Equality and pattern match operators (!= and !~ may not be defined as methods)
The table was from the 2001 Pickaxe book, but that's the same table as in the Ruby 1.9 Pickaxe book -- no reason to believe that this set of infix operators will ever change.
Ruby method for +=
The +=
operator is not associated to any method, it is just syntactic sugar, when you write a += b
the Ruby interpreter transform it to a = a + b
, the same is for a.b += c
that is transformed to a.b = a.b + c
. Thus you just have to define the methods x=
and x
as you need:
class Plane
def initialize
@moved = 0
@x = 0
end
attr_reader :x
def x=(x)
@x = x
@moved += 1
end
def to_s
"moved #{@moved} times, current x is #{@x}"
end
end
plane = Plane.new
plane.x += 5
plane.x += 10
puts plane.to_s
# => moved 2 times, current x is 15
What does +@ mean as a method in ruby
Ruby contains a few unary operators, including +
, -
, !
, ~
, &
and *
. As with other operators you can also redefine these. For ~
and !
you can simply just say def ~
and def !
as they don't have a binary counterpart (e.g. you cannot say a!b
).
However for -
and +
there is both a unary, and a binary version (e.g. a+b
and +a
are both valid), so if you want to redefine the unary version you have to use def +@
and def -@
.
Also note that there is a unary version of *
and &
as well, but they have special meanings. For *
it is tied to splatting the array, and for &
it is tied to converting the object to a proc, so if you want to use them you have to redefine to_a
and to_proc
respectively.
Here is a more complete example showing all kinds of the unary operators:
class SmileyString < String
def +@
SmileyString.new(self + " :)")
end
def -@
SmileyString.new(self + " :(")
end
def ~
SmileyString.new(self + " :~")
end
def !
SmileyString.new(self + " :!")
end
def to_proc
Proc.new { |a| SmileyString.new(self + " " + a) }
end
def to_a
[SmileyString.new(":("), self]
end
end
a = SmileyString.new("Hello")
p +a # => "Hello :)"
p ~a # => "Hello :~"
p *a # => [":(", "Hello"]
p !a # => "Hello :!"
p +~a # => "Hello :~ :)"
p *+!-~a # => [":(", "Hello :~ :( :! :)"]
p %w{:) :(}.map &a # => ["Hello :)", "Hello :("]
In your example the Module just simply defines an unary + operator, with a default value of not doing anything with the object (which is a common behaviour for the unary plus, 5
and +5
usually mean the same thing). Mixing in with any class would mean the class immediately gets support for using the unary plus operator, which would do nothing much.
For example (using ruby <=2.2
):
module M
def +@
self
end
end
p +"Hello" # => NoMethodError: undefined method `+@' for "Hello":String
class String
include M
end
p +"Hello" # => "Hello"
Note that in this example you can clearly see from the error message that the +@
method is missing from the class
Note that the above example will be different from Ruby 2.3, as the unary minus and plus are defined for Strings since that version, and they refer to returning a frozen and unfrozen string from the original.
Ruby overload + operator
Overloaded operator is resolved based on the class of the first operand, so if you wanted to overload the addition of simple integers, something like that should work:
class Fixnum
def +(other)
return self * other
end
end
I do not recommend you to actually do this, btw.
+= operator overloading in ruby
It is not possible to override =
, nor variants such as +=
. These are built in keywords and not methods such as +
.
If you change your patch from def +=(obj)
to def +(obj)
, you can still call r1 += r2
and it will have the same effect as if you'd patched +=
. This is because +=
is calling your patched +
method under the hood.
By the way, your +
method doesn't actually return a value so any time you call +=
it will always result in nil .... but it seems like this is still a WIP so hopefully you can sort that bit out.
Ruby: overload operator behaviour for some cases only
Both approaches posted here so far are a legacy Rails way, which is plain wrong. It relies on the fact that the class has no method called plus
and nobody will reopen the class to create a method called plus
. Otherwise things will go mad.
The correct solution is Module#prepend
:
Integer.prepend(Module.new do
def + other
case other
when Fixnum then special_behaviour
else super(other)
end
end
end)
Overloading :+= in Ruby
Unlike C++ or other languages with robust overrides, there's no such method as +=
in Ruby.
What's happening internally is a sort of expansion. For example:
x += n
If x
is a variable, then this is equivalent to:
x = x.send(:+, n)
If x=
is defined, then this is equivalent to:
send(:x=, x.send(:+, n))
Whatever you need to do to override must be to redefine x=
or +
on the class of x
.
Remember that the +
method should not modify x
. It's supposed to return a copy of x
with the modification applied. It should also return an object of the same class of x
for consistency.
Your method should look like:
def +(other)
# Create a new object that's identical in class, passing in any arguments
# to the constructor to facilitate this.
result = self.class.new(...)
result.value += other.to+f
result
end
How do you overload the operator in Ruby?
You need to do that from within a class. Like this:
class Whatever
attr_accessor :roles
def initialize
@roles = []
end
end
You can't really have a <<roles
method. You'd have to have an accessor for roles
that supports the <<
operator.
EDIT: I've updated the code. Now you can see how the <<
operator should be overloaded, but you can also do what the roles<<
part. Here's a small snippet of it's usage:
w = Whatever.new
w << "overload for object called"
# and overloads for the roles array
w.roles << "first role"
w.roles << "second role"
Overload = operator ruby?
Of course you can! It would help to google for Ruby's spaceship operator.
You need to include Comparable
module, and then implement the method. Take a look at simple example of overwriting <=>
: http://brettu.com/rails-daily-ruby-tips-121-spaceship-operator-example/
I'll take an example from the article:
class Country
include Comparable
attr_accessor :age
def initialize(age)
@age = age
end
def <=>(other_country)
age <=> other_country.age
end
end
For overloading the <=>
you don't need to include Comparable
module, however by including it, it "mixins" some useful methods to your Country
class with which you can perform comparisons.
Let's see some examples:
country1 = Country.new(50)
country2 = Country.new(25)
country1 > country2
# => true
country1 == country2
# => false
country1 < country2
# => false
country3 = Country.new(23)
[country1, country2, country3].sort
# => [country3, country2, country1]
But, if the Comparable
module was not included:
country1 > country2
# => NoMethodError: undefined method `>' for #<Country:...>
Good luck!
Related Topics
Error Occurs When Trying to Install Homebrew on a MAC for Ruby on Rails
Sublime Text 2 Doesn't Save Built SASS File
Tilt (Kramdown) Preventing Erb Processing When Rendering Markdown
Ruby, Value Bucketing, Beautify Code
Why Won't Ternary Operator Work with Redirect
Cannot Require 'Nokogiri' in Rails (But Works in Irb)
Loaderror - Cannot Open Shared Object File - File Is Present, But It Says No Such File
Why Sinatra Request Takes Em Thread
How to Write a Method That Counts the Most Common Substring in a String in Ruby
Sorting of 2D Array by Its Amount of in the Inner Elements
Check If String Is Repetition of an Unknown Substring
Encrypt Data Bag from Inside of Ruby Without Relying on Knife