How do I make a class generic for all Numeric Types?

前端 未结 3 1344
南笙
南笙 2020-12-29 04:28

I am trying to create a Vector class that is generic for all numeric types. my original attempt was to write a class for all Types like this:

class Vector3f(         


        
相关标签:
3条回答
  • 2020-12-29 04:58

    You can't. Not right now. Maybe when, and if, Numeric gets specialized.

    Say you get the simplest parameterized class possible:

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

    The method + will compile into something roughly like this:

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

    That's scalac -optimize -Xprint:jvm output. Now there are even subclasses for each specialized type, so that you can initialize a Vector3 without boxing, but as long as Numeric is not specialized, you can't go further.

    Well... you can write your own Numeric and specialize that, but, at that point, I'm not sure what you are gaining by making the class parameterized in first place.

    0 讨论(0)
  • 2020-12-29 05:09

    The short answer is: you can't get full performance. Or at least I haven't found anything that gives full performance. (And I have tried for a while in exactly this use case; I gave up and wrote a code generator instead, especially since you can't handle different vector sizes generically either.)

    I'd be delighted to be shown otherwise, but thus far everything I've tried has had a small (30%) to vast (900%) increase in runtime.


    Edit: here's a test showing what I mean.

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

    and the output:

    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 讨论(0)
  • 2020-12-29 05:21

    You probably want to use the typeclass pattern as described here: http://dcsobral.blogspot.com/2010/06/implicit-tricks-type-class-pattern.html

    Or, you can indirectly use by by using the Numeric trait http://www.scala-lang.org/api/current/scala/math/Numeric.html

    0 讨论(0)
提交回复
热议问题