2011-01-23 10 views
5

Quiero tener un vector general de clase abstracta/rasgo que especifica ciertos métodos, por ejemplo:jerarquía de clases correcto para 2D y 3D vectores de

trait Vec 
{ 
    def +(v:Vec):Vec 
    def *(d:Double):Vec 

    def dot(v:Vec):Double 
    def norm:Double 
} 

quiero tener Vec2D y Vec3D extienden Vec:

class Vec2D extends Vec { /* implementation */ } 
class Vec3D extends Vec { /* implementation */ } 

Pero, ¿cómo puedo, por ejemplo, hacer que Vec2D solo se pueda agregar a otros Vec2D y no a Vec3D?

Ahora mismo estoy implementando Vec2D y Vec3D sin un antecesor común Vec, pero esto se está volviendo tedioso con código duplicado. Tengo que implementar todas mis clases de geometría que dependen de estas clases (por ejemplo, Triangle, Polygon, Mesh, ...) dos veces, una para Vec2D y otra vez para Vec3D.

Veo las implementaciones de java: javax.vecmath.Vector2d y javax.vecmath.Vector3d no tienen un antecesor en común. ¿Cuál es el motivo de esto? ¿Hay alguna manera de superarlo en Scala?

Respuesta

5

Como requested, la forma más útil de diseñar el rasgo de base implica tanto la CRTP y la self-type annotation.

trait Vec[T <: Vec[T]] { this: T => 
    def -(v: T): T 
    def *(d: Double): T 

    def dot(v: T): Double 
    def norm: Double = math.sqrt(this dot this) 
    def dist(v: T) = (this - v).norm 
} 

Sin el tipo de auto, no es posible llamar this.dot(this) como dot espera un T; por lo tanto, debemos aplicarlo con la anotación.

Por otro lado, sin CRTP, vamos a no citan a norm en (this - v) como - devuelve un T y por lo tanto tenemos que asegurarnos de que nuestro tipo T tiene este método, por ejemplo, declare que Tes unVec[T].

4

No estoy seguro acerca de la sintaxis correcta de Scala, pero puede implementar el CRTP, es decir, definir el tipo real a través de un parámetro genérico.

trait Vec[V <: Vec[V]] { 
    def +(v:V):V 
    ... 
} 

class Vec2D extends Vec[Vec2D] { } 
class Vec3D extends Vec[Vec3D] { } 

class Polygon[V <: Vec[V]] { 
    ... 
} 
+0

el clavo. La sintaxis es correcta y todo! Supongo que Java no es compatible con esto (de lo contrario, ¿cuál es el problema con 'javax.vecmath')? – dsg

+0

En realidad, supongo que java admite esto: http://stackoverflow.com/questions/2382915/what-does-this-java-generics-paradigm-do-and-what-is-it-called – dsg

7

Puede utilizar los tipos de asistencia:

trait Vec[T] { self:T => 
    def +(v:T):T 
    def *(d:Double):T 

    def dot(v:T):Double 
    def norm:Double 
} 

class Vec2D extends Vec[Vec2D] { /* implementation */ } 
class Vec3D extends Vec[Vec3D] { /* implementation */ } 

Pero si ambas implementaciones son muy similares, también se podría tratar de abstraer sobre la Dimensión.

sealed trait Dimension 
case object Dim2D extends Dimension 
case object Dim3D extends Dimension 

sealed abstract class Vec[D <: Dimension](val data: Array[Double]) { 

    def +(v:Vec[D]):Vec[D] = ... 
    def *(d:Double):Vec[D] = ... 

    def dot(v:Vec[D]):Double = ... 
    def norm:Double = math.sqrt(data.map(x => x*x).sum) 
} 

class Vec2D(x:Double, y:Double) extends Vec[Dim2D.type](Array(x,y)) 
class Vec3D(x:Double, y:Double, z:Double) extends Vec[Dim3D.type](Array(x,y,z)) 

Por supuesto, depende de cómo se desea representar los datos, y si usted quiere tener casos mutables o inmutables. Y para aplicaciones del "mundo real" se debe considerar http://code.google.com/p/simplex3d/

+0

Los tipos propios te permiten para referirse a 'this', mientras que el patrón de CRTP en la respuesta de Darío no. – dsg

+0

@dsg: ¿Qué quieres decir con que no puedes referirte a 'this' con CRTP? – Debilski

+1

Correcto, no puedes. – Debilski

2

Hay un gran problema con tener un ancestro común con patrón CRTP en JVM. Cuando ejecuta el mismo código abstracto con diferentes implementaciones, JVM desoptimizará el código (sin enlining + llamadas virtuales). No lo notarás si solo pruebas con Vec3D, pero si pruebas tanto con Vec2D como con Vec3D verás una gran caída en el rendimiento. Además, el Análisis de escape no se puede aplicar al código de optimización (sin reemplazo escalar, sin eliminación de nuevas instancias). La falta de estas optimizaciones reducirá la velocidad de su programa en un factor de 3 (una estimación muy completa que depende de su código).

Pruebe algunos puntos de referencia que se ejecutan durante unos 10 segundos. En la misma prueba de ejecución con Vec2D, luego Vec3D, luego Vec2D, luego Vec3D nuevamente. Verá este patrón:

  • Vec2D ~ 10 segundos
  • Vec3D ~ 30 segundos
  • Vec2D ~ 30 segundos
  • Vec3D ~ 30 segundos
Cuestiones relacionadas