2009-12-06 4 views
11

Я возился с рубином и opengl для развлекательных целей, и я решил написать несколько 3D-векторных/плоскостных/и т. Д. Классов, чтобы доработать некоторые из математики.ruby ​​operator overloading question

упрощенный пример:

class Vec3 
    attr_accessor :x,:y,:z 

    def *(a) 
     if a.is_a?(Numeric) #multiply by scalar 
      return Vec3.new(@x*a, @y*a, @z*a) 
     elsif a.is_a?(Vec3) #dot product 
      return @x*a.x + @y*a.y + @z*a.z 
     end 
    end 
end 

v1 = Vec3.new(1,1,1) 
v2 = v1*5 #produces [5,5,5] 

, который все хорошо и денди, но я также хочу, чтобы иметь возможность написать

v2 = 5*v1 

, который требует добавления функциональности в Fixnum или Float или любой другой, но я не мог 't найти способ перегрузить или расширить умножение fixnum, не заменяя его полностью. возможно ли это в рубине? какие-нибудь советы?

(очевидно, я могу просто написать все мои умножений в правильном порядке, если мне нужно)

+0

Только для th e, измените '@ x * s, @ y * s, @ z * s' на' @ x * a, @ y * a, @ z * a', иначе ваш код будет сломан. –

+0

спасибо, скопированный код из 2-х мест сразу><должен быть исправлен – 2009-12-06 12:01:36

ответ

21

Использование принуждать является гораздо более эффективным подходом, чем обезьяна-латание основного класса:

class Vec3 
    attr_accessor :x,:y,:z 

    def *(a) 
     if a.is_a?(Numeric) #multiply by scalar 
      return Vec3.new(@x*a, @y*a, @z*a) 
     elsif a.is_a?(Vec3) #dot product 
      return @x*a.x + @y*a.y + @z*a.z 
     end 
    end 

    def coerce(other) 
     return self, other 
    end 
end 

если определить V, как v = Vec3.new то следующее будет работать: v * 5 и 5 * v Первый элемент, возвращаемый coerce (self) становится новым приемником для операции, а второй элемент (другой) становится параметром, поэтому 5 * v в точности эквивалентен v * 5

+1

+1 для принуждения. От лица лица, которое должно отлаживать ваш код, не делайте «monkeypatch» основных классов, если не требуется супер-пупер. – zenazn

+0

это отлично поработало для того, что мне нужно. если я столкнулся с аналогичным примером, который не может быть коммутативным, то я полагаю, что я буду использовать патч обезьяны, если это необходимо;) – 2009-12-06 12:18:38

+0

. Принуждение всегда должно возвращать 'self' в качестве второго аргумента! В противном случае вы испортите коммутативность аргументов. Вместо этого «принуждение» должно приводить аргумент к типу, который может быть умножен (например,: Vec3.new (другой, другой, другой) »). Очевидно, это не лишено собственных проблем. –

-1

Я считаю, что следующий будет делать то, что вы хотите, хотя banister's suggestion использовать coerce вместо обезьяны заплат Numeric является предпочтительным метод. Используйте этот метод только в случае необходимости (например, если вы хотите только , некоторые двоичные операнды должны быть транзитивными).

Fixnum.class_eval do 
    original_times = instance_method(:*) 
    define_method(:*) do |other| 
    if other.kind_of?(Vec3) 
     return other * self 
    else 
     return original_times.bind(self).call(other) 
    end 
    end 
end 
+1

mmm sexy :) Кстати, мне нужно было изменить первую строку на «Fixnum.class_eval do» или (эквивалент?) «Class Fixnum» – 2009-12-06 03:20:25

+1

это невозможно просто определить его непосредственно в классе Fixnum без class_eval и сделать регулярный def, а не define_method? – horseyguy

+0

ОК, этот код сумасшедший :) не только вам не нужен class_eval (обычное тело класса будет хорошо) вы делаете смешные вещи с несвязанными объектами метода, которые совершенно не нужны. Почему бы просто не использовать alias_method? ;) Не говоря уже о том, что обезьяна-паттинг основного класса - это огромная не-нет :) – horseyguy