2009-12-06 16 views
11

he estado jugando con ruby ​​y opengl para fines de entretenimiento, y decidí escribir algunas clases vector/avión/etc 3d para algunas de las matemáticas.pregunta de sobrecarga del operador de rubí

ejemplo simplificado:

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] 

el que todo lo fino y elegante, pero también quiero ser capaz de escribir

v2 = 5*v1 

la que exige agregar funcionalidad a Fixnum o flotar o lo que sea, pero yo no podía No encuentra una forma de sobrecargar o extender la multiplicación de Fixnum sin reemplazarlo por completo. ¿Es esto posible en ruby? ¿algun consejo?

(obviamente sólo puedo escribir todas mis multiplicaciones en el orden correcto si tengo que)

+0

Solo para th e grabar, cambie '@ x * s, @ y * s, @ z * s' a' @ x * a, @ y * a, @ z * a'; de lo contrario, su código estará roto. –

+0

gracias, código copiado de 2 lugares a la vez>

Respuesta

21

Uso de coaccionar es un enfoque mucho mejor que el mono-parchear una clase principal:

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 

si se define v como v = Vec3.new entonces el siguiente trabajo: v * 5 y 5 * v El primer elemento devuelto por coerce (self) se convierte en el nuevo receptor para la operación, y el segundo elemento (otro) se convierte en el parámetro, por lo que 5 * v es exactamente equivalente a v * 5

+1

+1 for coerce. En nombre de la persona que tiene que depurar el código, no pongas en parche las clases principales a menos que sea súper-absolutamente necesario. – zenazn

+0

esto funcionó muy bien para lo que necesitaba. si me encuentro con un ejemplo similar que no puede ser conmutativo, entonces supongo que voy a parche mono como sea necesario;) –

+0

Coerce siempre debe devolver 'self' como el segundo argumento! De lo contrario, estropea la conmutatividad de los argumentos. En cambio, 'coerce' debería convertir el argumento en un tipo que se pueda multiplicar (por ejemplo, por:' Vec3.new (other, other, other) '). Obviamente, esto no es sin sus propios problemas. –

-1

creo que el siguiente va a hacer lo que quiere, aunque banister's suggestion de usar coerce en lugar de mono-parches Numeric es una preferidas método. Utilice este método solo si es necesario (por ejemplo, si solo desea algunos operandos binarios para que sean transitivos).

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 :) por cierto, necesitaba cambiar la primera línea a "Fixnum.class_eval do" o (¿el equivalente?) "Clase Fixnum" –

+1

es no es posible solo para definirlo directamente en la clase Fixnum sin el class_eval, y para hacer una definición regular en lugar de definir_method? – horseyguy

+0

bien, este código es una locura :) no solo no necesitas un class_eval (un cuerpo de clase ordinario va a funcionar bien) estás haciendo cosas ridículas con objetos de método independientes que son completamente innecesarios. ¿Por qué no usar alias_method en su lugar? ;) Sin mencionar que el parche de mono de una clase principal es un gran no-no también :) – horseyguy