2011-01-20 3 views
23

Sto provando a creare una classe Vector generica per tutti i tipi numerici. mio tentativo originale era di scrivere una classe per tutti i tipi come questo:Come si crea una classe generica per tutti i tipi numerici?

class Vector3f(val x:Float, val y:Float, val z:Float) 

dal Scala supporta l'annotazione specializzata potrei usare questo per generare me queste classi per tutti i tipi numerici

class Vector3[A <: What?](val x:A,val y:A, val z:A) 

ma tutto Ho trovato come super tipo per i numeri era AnyVal, ma AnyVal non supporta + - * /. Quindi qual è il modo giusto per farlo, ma senza sacrificare le prestazioni dei tipi di numeri non condivisi?

+0

Si potrebbe dare un'occhiata a questa domanda: http://stackoverflow.com/questions/4436936/scala-compiler-not-recognizing-a-view-bound/4437336#4437336 – Madoc

risposta

15

Non è possibile. Non adesso. Forse quando, e se, Numeric si specializza.

Dire che si ottiene la classe più semplice possibile con parametri:

class Vector3[@specialized T](val x: T, val y: T, val z: T)(implicit num: Numeric[T]) { 
    def +(other: Vector3[T]) = new Vector3(num.plus(x, other.x), num.plus(y, other.y), num.plus(z, other.z)) 
} 

Il metodo + compilerà in qualcosa di grosso modo così:

override <specialized> def +$mcD$sp(other: Vector3): Vector3 = new Vector3$mcD$sp(
    scala.Double.unbox(
    Vector3$mcD$sp.this.Vector3$$num.plus(
     scala.Double.box(Vector3$mcD$sp.this.x()), 
     scala.Double.box(other.x$mcD$sp()))), 
    scala.Double.unbox(
    Vector3$mcD$sp.this.Vector3$$num.plus(
     scala.Double.box(Vector3$mcD$sp.this.y()), 
     scala.Double.box(other.y$mcD$sp()))), 
    scala.Double.unbox(
    Vector3$mcD$sp.this.Vector3$$num.plus(
     scala.Double.box(Vector3$mcD$sp.this.z()), 
     scala.Double.box(other.z$mcD$sp()))), 
    Vector3$mcD$sp.this.Vector3$$num); 

Questo è scalac -optimize -Xprint:jvm uscita. Ora ci sono anche sottoclassi per ogni tipo specializzato, in modo da poter inizializzare uno Vector3 senza boxe, ma finchè lo Numeric non è specializzato, non si può andare oltre.

Bene ... puoi scrivere il tuo Numeric e specializzarlo, ma, a quel punto, non sono sicuro di ciò che stai guadagnando rendendo la classe parametrizzata al primo posto.

6

Probabilmente si desidera utilizzare il modello di typeclass come descritto qui: http://dcsobral.blogspot.com/2010/06/implicit-tricks-type-class-pattern.html

In alternativa, è possibile utilizzare indirettamente dal utilizzando il tratto numerico http://www.scala-lang.org/api/current/scala/math/Numeric.html

+1

È un po 'strano che il post di blog collegato in questa risposta è stato scritto dal ragazzo che ha fornito una risposta più completa ma con punteggi più bassi a questa stessa domanda. :) –

+0

Sì, ho notato anche 'dcsobral' nel link del blog e avevo già visto la risposta di Daniel sopra. – javadba

8

La risposta breve è: non si può ottenere massime prestazioni . O almeno non ho trovato nulla che dà prestazioni complete. (E ho provato per un istante nello in questo caso d'uso: ho rinunciato e ho scritto un generatore di codice, soprattutto perché non si possono gestire genericamente formati vettoriali diversi.)

Sarei lieto di essere mostrato diversamente, ma finora tutto ciò che ho provato ha avuto un piccolo (30%) aumento (900%) in runtime.


Modifica: ecco un test che mostra cosa intendo.

object Specs { 
    def ptime[T](f: => T): T = { 
    val t0 = System.nanoTime 
    val ans = f 
    printf("Elapsed: %.3f s\n",(System.nanoTime-t0)*1e-9) 
    ans 
    } 
    def lots[T](n: Int, f: => T): T = if (n>1) { f; lots(n-1,f) } else f 

    sealed abstract class SpecNum[@specialized(Int,Double) T] { 
    def plus(a: T, b: T): T 
    } 

    implicit object SpecInt extends SpecNum[Int] { 
    def plus(a: Int, b: Int) = a + b 
    } 

    final class Vek[@specialized(Int,Double) T](val x: T, val y: T) { 
    def +(v: Vek[T])(implicit ev: SpecNum[T]) = new Vek[T](ev.plus(x,v.x), ev.plus(y,v.y)) 
    } 

    final class Uek[@specialized(Int,Double) T](var x: T, var y: T) { 
    def +=(u: Uek[T])(implicit ev: SpecNum[T]) = { x = ev.plus(x,u.x); y = ev.plus(y,u.y); this } 
    } 

    final class Veq(val x: Int, val y: Int) { 
    def +(v: Veq) = new Veq(x + v.x, y + v.y) 
    } 

    final class Ueq(var x: Int, var y: Int) { 
    def +=(u: Ueq) = { x += u.x; y += u.y; this } 
    } 

    def main(args: Array[String]) { 
    for (i <- 1 to 6) { 
     ptime(lots(1000000,{val v = new Vek[Int](3,5); var u = new Vek[Int](0,0); var i=0; while (i<100) { u = (u+v); i += 1 }; u})) 
     ptime(lots(1000000,{val v = new Veq(3,5); var u = new Veq(0,0); var i=0; while (i<100) { u = (u+v); i += 1 }; u})) 
     ptime(lots(1000000,{val v = new Uek[Int](3,5); val u = new Uek[Int](0,0); var i=0; while (i<100) { u += v; i += 1 }; u})) 
     ptime(lots(1000000,{val v = new Ueq(3,5); val u = new Ueq(0,0); var i=0; while (i<100) { u += v; i += 1 }; u})) 
    } 
    } 
} 

e l'uscita:

Elapsed: 0.939 s 
Elapsed: 0.535 s 
Elapsed: 0.077 s 
Elapsed: 0.075 s 
Elapsed: 0.947 s 
Elapsed: 0.352 s 
Elapsed: 0.064 s 
Elapsed: 0.063 s 
Elapsed: 0.804 s 
Elapsed: 0.360 s 
Elapsed: 0.064 s 
Elapsed: 0.062 s 
Elapsed: 0.521 s <- Immutable specialized with custom numeric 
Elapsed: 0.364 s <- Immutable primitive type 
Elapsed: 0.065 s <- Mutable specialized with custom numeric 
Elapsed: 0.065 s <- Mutable primitive type 
... 
+0

Hai provato a creare la tua classe 'Numeric' specializzata? –

+0

Non in 2.8.1. In precedenza in 2.8 (in ritardo 2.8.0 RC, IIRC) c'era qualche problema che non ricordo più che manteneva le prestazioni a livelli un po 'insignificanti. Credo che dovrei provare di nuovo. –

+0

Ho appena provato di nuovo. Sembra ok per le operazioni mutabili con Sun JVM, ma una volta che hai bisogno di creare nuovi oggetti, c'è una penalità di ~ 2x. (La JVM di JRockit mostra la stessa tendenza, ma tutte le temporizzazioni sono 2-3 volte peggiori, di solito, e possono solo a volte far funzionare il caso specializzato mutabile.) –