scala - ¿Cómo hago una clase genérica para todos los tipos numéricos?
scala annotations (3)
La respuesta corta es: no se puede obtener un rendimiento completo. O al menos no he encontrado nada que dé pleno rendimiento. (Y lo he intentado durante un tiempo exactamente en este caso de uso; me di por vencido y escribí un generador de código, especialmente porque tampoco puedes manejar diferentes tamaños de vectores de forma genérica).
Me encantaría que me mostraran lo contrario, pero hasta ahora todo lo que he intentado ha tenido un aumento pequeño (30%) a vasto (900%) en el tiempo de ejecución.
Edición: aquí hay una prueba que muestra lo que quiero decir.
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}))
}
}
}
y la salida:
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
...
Estoy intentando crear una clase Vector que es genérica para todos los tipos numéricos. mi intento original fue escribir una clase para todos los tipos como este:
class Vector3f(val x:Float, val y:Float, val z:Float)
ya que scala admite la anotación especializada, podría usar esto para generarme estas clases para todos los tipos numéricos
class Vector3[A <: What?](val x:A,val y:A, val z:A)
pero todo lo que encontré como un súper tipo para números fue AnyVal, pero AnyVal no admite + - * /. Entonces, ¿cuál es la forma correcta de hacer esto, pero sin sacrificar el rendimiento de los tipos de números sin caja?
Probablemente desee utilizar el patrón de clase de tipos como se describe aquí: http://dcsobral.blogspot.com/2010/06/implicit-tricks-type-class-pattern.html
O, puede usar indirectamente usando el rasgo numérico http://www.scala-lang.org/api/current/scala/math/Numeric.html
Usted no puede No ahora. Tal vez cuando, y si, Numeric
se especializa.
Digamos que obtienes la clase parametrizada más simple posible:
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))
}
El método +
se compilará en algo más o menos así:
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);
Eso es scalac -optimize -Xprint:jvm
output. Ahora hay incluso subclases para cada tipo especializado, de modo que puede inicializar un Vector3
sin boxeo, pero mientras Numeric
no esté especializado, no puede ir más lejos.
Bueno ... puedes escribir tu propio Numeric
y especializar eso, pero, en ese momento, no estoy seguro de lo que estás ganando al hacer que la clase esté parametrizada en primer lugar.