I have an Angle class that I want to behave like a float, with additional behavior. I have created the class to contain a float and proxy all unknown methods to it:
class Angle
include Math
def initialize(angle=0.0)
@angle = Float(angle)
# Normalize the angle
@angle = @angle.modulo(PI*2)
end
def to_f
@angle.to_f
end
# Other functionality...
def method_missing(message, *args, &block)
if block_given?
@angle.public_send(message, *args, &block)
else
@angle.public_send(message, *args)
end
end
end
It works fine. However when I try to use it with trig operations, e.g. Math.cos, I get:
> a = Angle.new(0.0)
=> #<Angle:0x00000000cdb220 @angle=0.0>
@angle=0.0
> Math.cos(a)
TypeError: can't convert Angle into Float
I know I can use Float(a) to convert to a float, but it's inconvenient since I want this class to behave like a float. Is there a way to automatically convert Angle to float in these cases?
Looking at the implementation of Math.cos, you can see it calls a macro called Need_Float, which then calls a function rb_to_float. Line 2441 of rb_to_float checks to see if the object passed in is of type Numeric. So it seems the only way to have your own class act as a float in the Math family of functions is to have it inherit from Numeric or a descendant of Numeric. Thus, this modification of your code works as expected:
class Angle < Numeric
include Math
def initialize(angle=0.0)
@angle = Float(angle)
# Normalize the angle
@angle = @angle.modulo(PI*2)
end
def to_f
@angle.to_f
end
# Other functionality...
def method_missing(message, *args, &block)
if block_given?
@angle.public_send(message, *args, &block)
else
@angle.public_send(message, *args)
end
end
end
if __FILE__ == $0
a = Angle.new(0.0)
p Math.cos(a)
end
I'm not sure what side effects inheriting from Numeric will have, but unfortunately this looks like the only way to have your code work the way you want it to.