像这样的类型类的目标Numeric
是为类型提供一组操作,以便您可以编写对具有该类型类实例的任何类型通用的代码。Numeric
提供一组操作及其子类Integral
and Fractional
另外还提供了更具体的(但它们也描述了更少的类型)。如果您不需要这些更具体的操作,您可以简单地在以下级别工作Numeric
,但不幸的是在这种情况下你这样做了。
让我们从mean
。这里的问题是,除法对于整数和分数类型意味着不同的东西,并且根本不为仅提供除法的类型提供除法。Numeric
. The 您已链接的答案 https://stackoverflow.com/a/6190665/334519来自 Daniel 的通过调度运行时类型来解决这个问题Numeric
实例,如果实例不是一个,则崩溃(在运行时)Fractional
or Integral
.
我不同意丹尼尔(或者至少五年前的丹尼尔)的观点,并说这并不是一个很好的方法——它既掩盖了真正的差异,又同时抛弃了很多类型安全。我认为存在三个更好的解决方案。
只为小数类型提供这些操作
您可能会认为取平均值对于整数类型没有意义,因为积分除法会丢失结果的小数部分,并且只为分数类型提供它:
def mean[T: Fractional](xs: Iterable[T]): T = {
val T = implicitly[Fractional[T]]
T.div(xs.sum, T.fromInt(xs.size))
}
或者使用漂亮的隐式语法:
def mean[T: Fractional](xs: Iterable[T]): T = {
val T = implicitly[Fractional[T]]
import T._
xs.sum / T.fromInt(xs.size)
}
最后一点语法要点:如果我发现我必须写implicitly[SomeTypeClass[A]]
为了获取对类型类实例的引用,我倾向于对上下文绑定进行脱糖([A: SomeTypeClass]
部分)来清理一下:
def mean[T](xs: Iterable[T])(implicit T: Fractional[T]): T =
T.div(xs.sum, T.fromInt(xs.size))
不过,这完全是一个品味问题。
返回具体的分数类型
你也可以做mean
返回具体的分数类型,例如Double
,然后简单地转换Numeric
在执行操作之前将值设置为该类型:
def mean[T](xs: Iterable[T])(implicit T: Numeric[T]): Double =
T.toDouble(xs.sum) / xs.size
或者,等效地但与toDouble
语法为Numeric
:
import Numeric.Implicits._
def mean[T: Numeric](xs: Iterable[T]): Double = xs.sum.toDouble / xs.size
这为整数和小数类型提供了正确的结果(最高精度为Double
),但代价是使您的操作不那么通用。
创建一个新类型类
最后,您可以创建一个新的类型类,为以下对象提供共享除法运算:Fractional
and Integral
:
trait Divisible[T] {
def div(x: T, y: T): T
}
object Divisible {
implicit def divisibleFromIntegral[T](implicit T: Integral[T]): Divisible[T] =
new Divisible[T] {
def div(x: T, y: T): T = T.quot(x, y)
}
implicit def divisibleFromFractional[T](implicit T: Fractional[T]): Divisible[T] =
new Divisible[T] {
def div(x: T, y: T): T = T.div(x, y)
}
}
进而:
def mean[T: Numeric: Divisible](xs: Iterable[T]): T =
implicitly[Divisible[T]].div(xs.sum, implicitly[Numeric[T]].fromInt(xs.size))
这本质上是原始版本的更有原则性的版本mean
- 您不是在运行时分派子类型,而是使用新的类型类来表征子类型。有更多代码,但不可能出现运行时错误(当然除非xs
是空的,等等,但这是所有这些方法都会遇到的正交问题)。
结论
在这三种方法中,我可能会选择第二种,对于您的情况来说,这似乎特别合适,因为您的variance
and stdDev
已经返回Double
。在这种情况下,整个事情将如下所示:
import Numeric.Implicits._
def mean[T: Numeric](xs: Iterable[T]): Double = xs.sum.toDouble / xs.size
def variance[T: Numeric](xs: Iterable[T]): Double = {
val avg = mean(xs)
xs.map(_.toDouble).map(a => math.pow(a - avg, 2)).sum / xs.size
}
def stdDev[T: Numeric](xs: Iterable[T]): Double = math.sqrt(variance(xs))
……你就完成了。
在真实的代码中我可能会看一个像这样的库Spire https://github.com/non/spire不过,而不是使用标准库的类型类。