如何在Scala中使Int扩展Double?

时间:2014-11-26 22:53:13

标签: scala inheritance integer double

我有一个基本特征:

trait NumberFactory {
   def createNumber(): Double
}

我可以通过说:

来实现这个特性
class RandomNumberFactory extends NumberFactory {
    def createNumber() = Random.nextDouble()
}

将NumberFactory专门化为一个特殊的IntFactory会很好。

class IntFactory extends NumberFactory {
    def createNumber(): Int = 42 // returns an integer
}

在我看来这是有道理的,因为在某种意义上每个整数都是一个双精度数(每个整数都是实数)。但是,Int和Double似乎是不变的。这里的解决方案是什么?

在我的实例中,NumberFactory包含多个变量,它有两个实现:一个包含变量为整数,另一个包含实数。如果我有一个NumberFactory的引用,可以返回一个实数,但是在IntFactory的情况下,我需要一个整数。

3 个答案:

答案 0 :(得分:3)

Scala(以及几乎所有其他语言)中的继承意味着子类型关系:如果A extends B,那么在任何可以使用B的地方,都可以使用A。可应用于B的每项操作也适用于A。对于数据结构,这意味着子内容是父内容的超集;通常可以虚拟调度操作以实现专业化。

但是,Double / Int对所有这些都没有意义。虽然Double覆盖了Int的整个范围(因为它的尾数是53位,几乎是整个Int的两倍),但它们的内部表示完全不同,也不是另一方的超集。

如果数字类型使用虚拟调度,这可能不重要。毕竟,您可以覆盖+ / - / ... Int使行为方式不同,使用Int内部结构,不是吗?但是,这意味着每一个数值都应该携带一个元数据(一个虚拟表和许多与JVM相关的东西),这将大于值本身!而且,每个数值运算都应该通过虚拟表而不是简单的汇编操作。

对数字类型进行虚拟分派的另一个问题是:

def divide(x: Double, y: Double): Double {
  x / y
}

let (a: Int, b: Int) = (1, 2)
assert(divide(a, b) == 0.5)

当然,你会期望divide()返回0.5,对吧?但是,对于虚拟调度,它将返回0,因为实际除法是使用自己的除法方法在Int上执行的!如果我们混合DoubleInt会怎样?

divide(1: Int, 2: Double) == ?

这里应该使用哪些规则?也许应该采用某种自动转换?

您会看到,Int Double的子类型会带来很多关于如何正确有效地执行此操作的问题。统一数值类型和操作的其他方法通常不基于类和继承,但JVM在这方面相当严格。

您真正需要的取决于您的实际计划。最简单的方法是从42方法返回文字Double(它将自动用作Double):

class IntFactory extends NumberFactory {
    def createNumber(): Double = 42
}

使用类型参数的建议也适用于其他用例。

答案 1 :(得分:0)

我会使用参数多态,因此类型在变体上变得通用,并且它仍然利用类型安全性。示例代码:

import scala.util.Random

trait NumberFactory[T] {
  def createNumber(): T
}
class RandomNumberFactory extends NumberFactory[Double] {
  def createNumber() = Random.nextDouble()
}
class IntFactory extends NumberFactory[Int] {
  def createNumber() = 42
}

val randomNumberFactory : NumberFactory[Double] = new RandomNumberFactory()
val intFactory : NumberFactory[Int] = new IntFactory()
val doubleValue1 :Double = randomNumberFactory.createNumber
val intValue :Int = intFactory.createNumber
val doubleValue2 :Double = intFactory.createNumber      // Scala takes care of implicit conversion
val intValue2 :Int = randomNumberFactory.createNumber() // Compilation error; not every real number is integer

答案 2 :(得分:0)

这是使用Numeric类型的解决方案(与Suztomo描述的代码相同):

trait NumberFactory[A] {
  def createNumber(): A
}

class RandomDoubleFactory extends NumberFactory[Double] {
  def createNumber(): Double = scala.util.Random.nextDouble()
}

class IntFactory extends NumberFactory[Int] {
  def createNumber(): Int = 42
}

// example use case
import scala.math.Numeric
import scala.math.Numeric.Implicits._

def compute[A: Numeric](factory: NumberFactory[A], x: A): A =
  x * factory.createNumber() + x

compute(new RandomDoubleFactory, 42.3d)
compute(new IntFactory, 42)

虽然数字类型类有一些限制,例如没有除法。尖顶的数字类型似乎有一个div,我没有挖掘太多。

[A: Numeric]部分是context bound。 scala库已经提供了数字实例,因此您不必实现它们。

Implicits导入允许您在A上拥有常用运算符(例如*+等)。否则你将不得不写出这样丑陋的东西:implicitly[A].plus(x, factory.createNumber())