2010-08-11 7 views
1

J'écris quelques classes simples Vector et Matrix. Ils ressemblent à ceci:Comment spécifier un type lié pour Float et Double sur un type générique dans Scala?

// Vector with Floats 
case class Vector3f(x: Float, y: Float, z: Float) { 
    def +(v: Vector3f) = Vector3f(x + v.x, y + v.y, z + v.z) 
} 

// Vector with Doubles 
case class Vector3d(x: Double, y: Double, z: Double) { 
    def +(v: Vector3d) = Vector3d(x + v.x, y + v.y, z + v.z) 
} 

Si je vais avec des méthodes et des autres classes comme Point3f/j, Vector4f/j, Matrix3f/j, Matrix4f/d ... cela va être beaucoup de travail. Uff ... Donc, je pensais que les génériques pourraient aider ici et supprimer la redondance de mon code de base. Je pensais à quelque chose comme ceci:

// first I define a generic Vector class 
case class Vector3[@specialized(Float, Double) T](x: T, y: T, z: T) { 
    def +(v: Vector3[T]) = new Vector3[T](x + v.x, y + v.y, z + v.z) 
} 

// than I use some type aliases to hide the generic nature 
type Vector3f = Vector3[Float] 
type Vector3d = Vector3[Double] 

L'idée est que le compilateur génère des classes spécialisées scala pour Vector3 [Float] et Vector3 [Double] similaire en C++ modèle ferait. Malheureusement je dois mettre un type lié sur le paramètre de type [T] de la classe Vector3 tel que l'opérateur + est défini sur T. Ma question: Comment puis-je écrire Vector3 [Float] qu'il a les mêmes caractéristiques de performance que Vector3f? Contexte: Je voudrais utiliser les classes Vector3f/Vector3d dans le code de détection de collision ... donc la performance est importante pour moi.

+0

Vous pouvez vérifier http://scala-programming-language.1934581.n4.nabble.com/Who-wants-to-take-Numeric-T-seriously-over-a-1-3-year- timescale-td2009520.html pour quelques idées. – mkneissl

+0

Merci pour ce lien! Ce fut une lecture très utile pour moi. – gruenewa

Répondre

4

Utilisez un contexte lié de Fractional:

case class Vector3[@specialized(Float, Double) T : Fractional](x: T, y: T, z: T) { ... 

puis dans le corps de la classe, obtenir une instance des opérateurs arithmétiques:

val fractOps = implicitly[Fractional[T]] 

importer enfin ses membres dans le cadre de la class:

import fractOps._ 

Par la suite, vous pouvez écrire des opérations infixes ordinaires sur des valeurs de type T utilisé dans la classe. Malheureusement, vous devrez utiliser fractOps.div(a, b) au lieu de a/b pour la division.

+0

Merci pour votre solution, mais il semble y avoir une pénalité de performance. L'addition vectorielle public Vector3f $ plus (Vector3f v) {return new Vector3f (x() + v.x(), y() + v.y(), z() + v.z()); } devient public Vector3 $ plus (Vector3 v) {return new Vecteur3 (fractOps(). Plus (x(), vx()), fractOps(). Plus (y(), vy()), fractOps() .plus (z(), vz()), this.Vector3 $$ preuve $ 1); } Je suppose que je ne devrais pas utiliser de génériques pour implémenter des classes vectorielles et matricielles. – gruenewa

+0

@gruenewa: Vous devez regarder le bytecode et comprendre ce que peuvent faire les machines virtuelles Java à code natif (HotSpot, par exemple). Je pense qu'il est probable que ce sera tout le code natif en ligne dans une application en cours d'exécution. Cependant, sans la spécialisation, il y aura la boxe de la primitive Float et Double, ce qui peut être suffisant pour empêcher le compilateur JIT d'optimiser complètement le code. –

+0

@gruenewa Avez-vous essayé de mesurer s'il y a une différence de performance notable? C'est la seule façon de savoir avec certitude - il se peut que scalac et/ou le JIT optimisent les choses pour qu'il n'y ait pas de différence significative. – Jesper

Questions connexes