2011-12-10 72 views
3

我有一個角度類,我想表現得像一個浮動,具有額外的行爲。我創建了類包含一個浮動和代理所有未知的方法:如何自動將類實例轉換爲Ruby中的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 

我知道我可以使用浮動(一)轉換爲浮動,但因爲我想這個類的行爲像一個漂浮它的不方便。有沒有辦法在這些情況下自動轉換角度浮動?

回答

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 

我不知道有什麼副作用從數字繼承都會有,但不幸的是這看起來像有你的代碼的工作,你想要的方式的必由之路至。

+1

無法角度實現' to_float'? –

+0

我期望有一個類似to_ary和to_proc的to_float強制方法,但它不會出現ruby。 – phiggy

+0

有趣的解決方法。我可能需要確保調用Float方法而不是Numeric方法。 –

0

這就是我自己想出來的。數學是我真正感興趣的唯一的模塊,所以我可以爲它代理:從上面

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 

現在用角類定義:

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