Как сделать класс универсальным для всех числовых типов? - PullRequest
24 голосов
/ 21 января 2011

Я пытаюсь создать класс Vector, общий для всех числовых типов. моей первоначальной попыткой было написать класс для всех типов, например так:

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

поскольку scala поддерживает специализированную аннотацию, я мог бы использовать ее для создания этих классов для всех числовых типов

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

но все, что я нашел как супертип для чисел, было AnyVal, но AnyVal не поддерживает + - * /. Так какой же правильный способ сделать это, но без ущерба для производительности типов номеров без коробок?

Ответы [ 3 ]

16 голосов
/ 21 января 2011

Вы не можете.Не сейчас.Может быть, когда и если Numeric станет специализированным.

Допустим, вы получите самый простой параметризованный класс:

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

Метод + скомпилируется во что-то вроде этого:

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

Это scalac -optimize -Xprint:jvm вывод.Теперь есть даже подклассы для каждого специализированного типа, так что вы можете инициализировать Vector3 без упаковки, но пока Numeric не специализирован, вы не можете идти дальше.

Ну ...Вы можете написать свой собственный Numeric и специализировать его, но в тот момент я не уверен, что вы получите, сделав класс параметризованным в первую очередь.

8 голосов
/ 21 января 2011

Короткий ответ: вы не можете получить полную производительность. Или, по крайней мере, я не нашел ничего, что дало бы полную производительность. (И я какое-то время пытался в точно использовать этот вариант; вместо этого я отказался и написал генератор кода, тем более что вы не можете обрабатывать и другие размеры векторов вообще.)

Я был бы рад показать иначе, но пока все, что я пробовал, имело небольшое (от 30%) до огромного (900%) увеличение времени выполнения.


Редактировать: вот тест, показывающий, что я имею в виду.

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

и вывод:

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
...
6 голосов
/ 21 января 2011

Вы, вероятно, хотите использовать шаблон класса типов, как описано здесь: http://dcsobral.blogspot.com/2010/06/implicit-tricks-type-class-pattern.html

Или, вы можете косвенно использовать с помощью числовой черты http://www.scala -lang.org / api / current / scala / math / Numeric.html

...