我有一个基本特征:
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的情况下,我需要一个整数。
答案 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
上执行的!如果我们混合Double
和Int
会怎样?
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())
。