Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to perform reverse operators in Ruby? I have implemented the operator '+' for my class A so A + 2 works well. Problem is: 2 + A does not work [duplicate]

Tags:

ruby

How can I / should I perform reverse operators in Ruby? For example, I have implemented the operator '+' for my object A so now A + 2 works well. Problem is: 2 + A does not work :( Python provides __radd__ to address this issue. How should I attack this issue with Ruby?

Do I have to override the '+' operator of Integer? How about if it is a float, do I have to override the '+' operator from Float too? I like Ruby more than Python (strictly a personal and subjective opinion) but in Python you can simply do:

def __radd__(self, other): # other + self
    return self + other

Python provides __add__ and __radd__ that can be overridden. Does Ruby provide something similar?

Hoping that there is a quick and easy solution for that in Ruby, as 99.9% of the times there is.

Thanks!

like image 660
CrystalRuby3000 Avatar asked Sep 13 '25 06:09

CrystalRuby3000


1 Answers

In Python __radd__, __rsub__, and friends are used as follows: Docs

These methods are called to implement the binary arithmetic operations (+, -, *, @, /, //, %, divmod(), pow(), **, <<, >>, &, ^, |) with reflected (swapped) operands. These functions are only called if the left operand does not support the corresponding operation and the operands are of different types. For instance, to evaluate the expression x - y, where y is an instance of a class that has an __rsub__() method, type(y).__rsub__(y, x) is called if type(x).__sub__(x, y) returns NotImplemented.

Ruby does not have something exactly like this (nor this explicit); however ruby can perform a similar process using #coerce to ensure interoperability with instances of other numeric classes.

Let's assume A looks like this

class A 
  attr_reader :attr
  def initialize(a) 
    @attr = a 
  end 
 
  def +(other)
    other = other.attr if other.is_a?(A)
    A.new(attr + other)
  end 
end

Usage:

a = A.new(12)
a + 2 
#=> #<A:0x00007fb2942c7f38 @attr=14>
a + a 
#=> #<A:0x00007fb294156eb0 @attr=24>
2 + a 
#=> `+': A can't be coerced into Integer (TypeError)

In this case you want to be able to use Integer#+ with A as an argument. To do this you need to define A#coerce

class A 
  def coerce(other) 
    [A.new(other), self]
  end 
end 
a = A.new(10) 
2 + a 
#=> #<A:0x00007fb2942e55d8 @attr=12>

If you would rather 2 + a return an Integer you can change the Array provided by coerce

def coerce(other) 
  [other,attr] 
end

a = A.new(5) 
2 + a 
#=> 7  

What happens here (in simplified terms) is Integer#+ will see if it can add itself with the instance of A. Since it does not understand what an A is it will then call the coerce method and try the same message again with the return values.

It pseudo works like this

arg1 = 2 
arg2 = A.new(12) 
message = :+ 
begin
  arg1.public_send(message,arg2) 
rescue TypeError 
  arg1, arg2 = arg2.coerce(arg1)
  retry
end 
like image 87
engineersmnky Avatar answered Sep 14 '25 22:09

engineersmnky