2010-05-09 36 views
56

據說,當我們有一個類Point,並且知道如何像下面的執行point * 3在Ruby中,coerce()實際上是如何工作的?

class Point 
    def initialize(x,y) 
    @x, @y = x, y 
    end 

    def *(c) 
    Point.new(@x * c, @y * c) 
    end 
end 

point = Point.new(1,2) 
p point 
p point * 3 

輸出:

#<Point:0x336094 @x=1, @y=2> 
#<Point:0x335fa4 @x=3, @y=6> 

但隨後,

3 * point 

不瞭解:

Point不能被強迫FixnumTypeError

因此,我們需要進一步定義一個實例方法coerce

class Point 
    def coerce(something) 
    [self, something] 
    end 
end 

p 3 * point 

輸出:

#<Point:0x3c45a88 @x=3, @y=6> 

因此,它是表示3 * point3.*(point)相同。也就是說,實例方法*採用參數point並在對象3上調用。現在

,因爲這種方法*不知道怎麼乘一個點,所以

point.coerce(3) 

將被調用,並取回一個數組:

[point, 3] 

然後*是一次再次申請它,這是真的嗎?

現在,可以理解,我們現在有一個新的Point對象,如Point類的實例方法*所執行的。

的問題是:

  1. 誰調用point.coerce(3)?它是自動的Ruby,還是方法Fixnum捕捉異常內的一些代碼?或者是通過case聲明,當它不知道其中一種已知類型時,則請致電coerce

  2. 請問coerce總是需要返回2個元素的數組嗎?它可以不是陣列嗎?或者它可以是3個元素的數組?

  3. 而原則運算符(或方法)*然後將在元素0上調用元素1的參數? (元素0和元素1是coerce返回的數組中的兩個元素。)誰做的?它是由Ruby完成還是由Fixnum中的代碼完成?如果是通過Fixnum中的代碼完成的,那麼這是一個人們在強制執行時遵循的「約定」?

    所以不可能是在Fixnum*做這樣的事情代碼:

    class Fixnum 
        def *(something) 
        if (something.is_a? ...) 
        else if ... # other type/class 
        else if ... # other type/class 
        else 
        # it is not a type/class I know 
         array = something.coerce(self) 
         return array[0].*(array[1]) # or just return array[0] * array[1] 
        end 
        end 
    end 
    
  4. 所以這是真的很難添加一些Fixnum的實例方法coerce?它已經有很多在它的代碼,我們不能只是添加幾行,以增強它(但我們會永遠想?)

  5. Pointcoerce是非常通用的,它與*工作或+,因爲它們是傳遞性的。如果不可傳遞的,比如如果我們定義點減Fixnum對象是什麼:

    point = Point.new(100,100) 
    point - 20 #=> (80,80) 
    20 - point #=> (-80,-80) 
    
+1

這是一個很好的問題!我很高興我找到了它,因爲這一直困擾着我,直到現在,我不認爲它是可以解決的! – sandstrom 2011-10-06 17:49:40

+0

一個很好的問題。感謝您的推薦。我敢肯定,這將節省很多工程師的困惑時間。 – VaidAbhishek 2012-10-09 22:04:47

回答

39

簡短的回答:退房how Matrix is doing it

的想法是,coerce回報[equivalent_something, equivalent_self],其中equivalent_something是一個對象基本上等同於something但知道如何做你Point類操作。在Matrix庫中,我們構造了一個來自任何Numeric對象的Matrix::Scalar,並且該類知道如何在MatrixVector上執行操作。

爲了解決你的觀點:

  1. 是的,這是Ruby的直接(檢查rb_num_coerce_bin in the source通話),但如果你想你的代碼被他人擴展自己的類型應該做太多。例如,如果您的Point#*通過了一個它無法識別的參數,那麼您可以通過調用arg.coerce(self),將該參數詢問coerce本身爲Point

  2. 是的,它必須是2個元素的數組,使得b_equiv, a_equiv = a.coerce(b)

  3. 是。 Ruby的確使用它內置的類型,你應該也對自己的自定義類型,如果你想成爲擴展:

    def *(arg) 
        if (arg is not recognized) 
        self_equiv, arg_equiv = arg.coerce(self) 
        self_equiv * arg_equiv 
        end 
    end 
    
  4. 的想法是,你不應該修改Fixnum#*。如果它不知道該怎麼做,例如因爲參數是Point,那麼它會通過調用Point#coerce來問你。

  5. 傳遞性(或實際交換性)不是必需的,因爲操作符總是以正確的順序調用。這只是撥打coerce,它暫時恢復收到的和參數。沒有,從而確保了像+==運營商的交換性內建機制,等等

如果有人能拿出一個簡潔,準確和清晰的描述,以提高官方文檔,發表評論!

+0

嗯,不傳遞實際上有什麼區別? 例如,請參閱http://stackoverflow.com/questions/2801241/in-ruby-how-to-implement-20-point-and-point-20-using-coerce – 2010-05-10 09:01:18

+0

不,傳遞性不起任何作用而Ruby並不認爲'a-b'和' - (b-a)'或者類似的東西是一樣的,甚至不是'a + b == b + a'。什麼讓你相信我錯了?你檢查了MRI的來源嗎?爲什麼不嘗試遵循我指出的方向? – 2010-05-10 13:29:04

+0

我認爲OP意味着「對稱」而不是「傳遞」。在任何情況下,**我都想知道如何編寫'coerce',這樣像'-'這樣的非對稱運算符只能在一個方向上實現,同時保持對稱運算符的雙向運行。換句話說,'a + 3 == 3 + a'和'3 + a - 3 == a',但是'3 - a'會產生一個錯誤。 – 2013-03-30 19:36:11

2

我找到可交換打交道時,自己經常沿着這種模式編寫代碼:

class Foo 
    def initiate(some_state) 
    #... 
    end 
    def /(n) 
    # code that handles Foo/n 
    end 

    def *(n) 
    # code that handles Foo * n 
    end 

    def coerce(n) 
     [ReverseFoo.new(some_state),n] 
    end 

end 

class ReverseFoo < Foo 
    def /(n) 
    # code that handles n/Foo 
    end 
    # * commutes, and can be inherited from Foo 
end 
相關問題