scala 如何为所有数字类型创建通用类?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4753629/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 02:44:48  来源:igfitidea点击:

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

scalapolymorphismtype-parameter

提问by Arne

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:

我正在尝试创建一个适用于所有数字类型的 Vector 类。我最初的尝试是为所有类型编写一个类,如下所示:

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

since scala supports the specialised annotation I could use this to generate me these classes for all numeric types

由于 Scala 支持专门的注释,我可以使用它为所有数字类型生成这些类

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

but everything I found as a super type for numbers was AnyVal, but AnyVal does not support + - * /. So what is the right way to do this, but without sacrificing the performance of unboxed number types?

但是我发现作为数字的超类型的所有内容都是 AnyVal,但 AnyVal 不支持 + - * /。那么,在不牺牲未装箱数字类型的性能的情况下,这样做的正确方法是什么?

采纳答案by Daniel C. Sobral

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

你不能。不是现在。也许什么时候,如果,Numeric变得专业化。

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:jvmoutput. Now there are even subclasses for each specialized type, so that you can initialize a Vector3without boxing, but as long as Numericis not specialized, you can't go further.

那就是scalac -optimize -Xprint:jvm输出。现在甚至每个特化类型都有子类,这样你就可以Vector3不用装箱初始化a ,但只要Numeric不是特化,就不能更进一步。

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

嗯……你可以自己编写Numeric并专门化它,但是,在这一点上,我不确定首先将类参数化会获得什么。

回答by Rex Kerr

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 exactlythis 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.

我很高兴以其他方式显示,但到目前为止,我尝试过的所有内容的运行时间都有小幅 (30%) 到巨大 (900%) 的增加。



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
...

回答by Mitch Blevins

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

您可能想要使用此处描述的 typeclass 模式: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

或者,您可以通过使用 Numeric trait http://www.scala-lang.org/api/current/scala/math/Numeric.html间接使用