Reputation: 549
My question is: how do I overload an operator on a builtin class (such as Integer.new.+) but only for some cases, depending on the class of the second operand
This is the behaviour I'm looking for:
myObject = myClass.new
1 + myObject #=> special behaviour
1 + 2 #=> default behaviour (3)
For example, in Python I would define a __radd__
method on myClass to override case 1.
I've tried using super
but apparently Numeric
doesn't have operator methods.
Ideally, what I'm looking for is a way to extract the +
method and rename it.
Like this:
class Integer
self.plus = self.+ # you know what i mean, I don't know how else to express this.
# I also know that methods don't work like this, this is just to
# illustrate a point.
def + other
other.class == myClass ? special behaviour : self.plus other
end
end
Thanks for your help
Upvotes: 4
Views: 493
Reputation: 3870
Yes, you can override the behavior of almost anything in the standard library to achieve an outcome, but that's going to hurt understanding of the code and come back to bite you sometime in the future.
In this particular case, Fixnum#+
is designed to take a numeric value and return a numeric result. If we want to define our own classes to interact with Fixnum#+
, we need to understand the design contract and adhere to it.
The general convention in Ruby is to use duck typing. We don't care about the class of the object, we just care whether it behaves like / can be converted to the object we want. Eg:
class StringifiedNumber
def initialize(number)
@number = number
end
# String#+ calls to_str on any object passed to it
def to_str
# replace with number to string parsing logic
"one hundred"
end
end
> "total: " + StringifiedNumber.new(100)
=> "total: one hundred"
Things are a bit more complex with numbers since you may mix integers, floats, complex numbers, etc. The convention to handle this is to define a coerce
method which returns two elements of the same type which are then used to perform the requested operation.
class NumberfiedString
def initialize(string)
@string = string
end
def to_i
# replace with complicated natural language parsing logic
100
end
def +(other_numberfied_string)
NumberfiedString.new(self.to_i + other_numberfied_string.to_i)
end
# For types which are not directly supported,
# Fixnum#+(target) will call the equivalent of
# target.coerce[0] + target.coerce[1]
def coerce(other)
[NumberfiedString.new(other.to_s), self]
end
end
> NumberfiedString.new("one hundred") + NumberfiedString.new("one hundred")
=> #<NumberfiedString:0x007fadbc036d28 @string=200>
> 100 + NumberfiedString.new("one hundred")
=> #<NumberfiedString:0x007fadbc824c88 @string="200">
To answer OP's follow up question:
Is there no equivalent to Python's radd and related methods? (Where, if the first operand doesn't support the operation or the types, the second operand takes over)
class MyClass
def +(other)
puts "called +"
end
def coerce(other)
[self, other]
end
end
> 1 + MyClass.new
called +
=> nil
Upvotes: 3
Reputation: 120990
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)
Upvotes: 6