如何在Ruby中自动将类实例转换为Float?

时间:2011-12-10 19:03:38

标签: ruby casting floating-point proxy-classes

我有一个Angle类,我想表现得像一个浮点数,还有其他行为。我创建了一个类来包含一个float并代理所有未知的方法:

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

工作正常。但是,当我尝试将其与trig操作一起使用时,例如Math.cos,我得到:

> a = Angle.new(0.0)
 => #<Angle:0x00000000cdb220 @angle=0.0> 
@angle=0.0
> Math.cos(a)
TypeError: can't convert Angle into Float

我知道我可以使用Float(a)转换为浮点数,但这很不方便,因为我希望这个类的行为像浮点数一样。在这些情况下,有没有办法自动将Angle转换为float?

2 个答案:

答案 0 :(得分:3)

查看implementation of Math.cos,您可以看到它调用名为Need_Float的宏which then calls a function rb_to_floatLine 2441 of rb_to_float checks to see if the object passed in is of type Numeric。因此,似乎让自己的类在Math函数族中充当浮点数的唯一方法是让它继承Numeric或Numeric的后代。因此,代码的这种修改按预期工作:

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

我不确定从Numeric继承的副作用会有什么,但不幸的是,这似乎是让代码以您希望的方式工作的唯一方法。

答案 1 :(得分:0)

这就是我自己想出来的。 Math是我真正感兴趣的唯一模块,因此我可以为它编写代理:

module Stdlib; end
::Stdlib::Math = ::Math
module AngleMath
  # Copy constants
  Stdlib::Math.constants.each do |c|
    self.const_set(c, ::Stdlib::Math.const_get(c))
  end

  def self.map_angles_to_floats(args)
    args.map do |a|
      a.kind_of?(Angle)? a.to_f: a
    end
  end

  def self.method_missing(message, *args, &block)
    if block_given?
      ::Stdlib::Math.public_send(message, *map_angles_to_floats(args), &block)
    else
      ::Stdlib::Math.public_send(message, *map_angles_to_floats(args))
    end
  end
end
::Math = AngleMath

现在使用上面的Angle类定义:

a = Angle.new(0.0)
# => #<Angle:0x00000000e6dc28 @angle=0.0> 
Math.cos(a)
# => 1.0