2011-01-23 10 views
5

voglio avere un generale vettore classe astratta/caratteristica che specifica certi metodi, ad esempio:gerarchia delle classi corretta per la progettazione 2D e 3D vettori

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

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

voglio avere Vec2D e Vec3D estendono Vec:

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

Ma come posso, ad esempio, fare in modo che Vec2D possa essere aggiunto solo ad altri Vec2D e non a Vec3D?

In questo momento mi sto solo attuazione Vec2D e Vec3D senza un comune antenato Vec, ma questo sta diventando noiosa con il codice di duplicato. Devo implementare tutte le mie classi geometriche che dipendono da queste classi (ad esempio Triangle, Polygon, Mesh, ...) due volte, una volta per Vec2D e nuovamente per Vec3D.

Vedo le implementazioni Java: javax.vecmath.Vector2d e javax.vecmath.Vector3d non hanno un antenato comune. Qual è la ragione di questo? C'è un modo per superarlo in scala?

risposta

5

Come requested, il modo più utile di progettare il tratto di base coinvolge sia il CRTPe il 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 
} 

Senza l'auto-tipo, non è possibile chiamare this.dot(this) come dot aspetta una T; quindi dobbiamo applicarlo con l'annotazione.

D'altra parte, senza CRTP, noi non riusciamo a chiamare norm su (this - v) come - restituisce un T e quindi abbiamo bisogno di fare in modo che il nostro tipo T ha questo metodo, per esempio Dichiara che Tè unVec[T].

4

non sono sicuro sulla sintassi corretta Scala, ma è possibile implementare la CRTP, vale a dire definire il tipo reale attraverso un parametro generico.

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

Spot on. La sintassi è corretta e tutto! Suppongo che java non supporti questo (altrimenti qual è l'accordo con 'javax.vecmath')? – dsg

+0

In realtà, suppongo che java supporti questo: http://stackoverflow.com/questions/2382915/what-does-this-java-generics-paradigm-do-and-what-is-it-called – dsg

7

È possibile utilizzare i tipi di auto:

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 */ } 

Ma se entrambe le implementazioni sono molto simili, si potrebbe anche provare a astratto su dimensione.

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)) 

Naturalmente dipende da come si vuole rappresentare i dati, e se si desidera avere le istanze mutabili e immutabili. E per le applicazioni "mondo reale" si dovrebbe considerare http://code.google.com/p/simplex3d/

+0

I tipi di auto ti consentono fare riferimento a 'this', mentre il modello CRTP nella risposta di Dario no. – dsg

+0

@dsg: cosa vuoi dire che non puoi fare riferimento a 'this' con CRTP? – Debilski

+1

Giusto, non puoi. – Debilski

2

C'è un grosso problema con avere un antenato comune con pattern CRTP su JVM. Quando esegui lo stesso codice astratto con diverse implementazioni, JVM de-ottimizza il codice (nessuna inlining + chiamate virtuali). Non lo noterai se esegui solo test con Vec3D, ma se esegui il test sia con Vec2D che con Vec3D vedrai un enorme calo di prestazioni. Inoltre, Escape Analysis non può essere applicato al codice di de-ottimizzazione (nessuna sostituzione scalare, nessuna elimitazione di nuove istanze). La mancanza di queste ottimizzazioni rallenterà il tuo programma di un fattore 3 (ipotesi molto arrotondata che dipende dal tuo codice).

Prova alcuni parametri di riferimento che vengono eseguiti per circa 10 secondi. Nello stesso test di esecuzione con Vec2D, quindi Vec3D, quindi Vec2D, quindi nuovamente Vec3D. Vedrete questo schema:

  • Vec2D ~ 10 secondi
  • Vec3D ~ 30 secondi
  • Vec2D ~ 30 secondi
  • Vec3D ~ 30 secondi