Scala:通用加权平均函数

ShS*_*ShS 8 generics scala implicit implicit-conversion

我想实现一个通用加权平均函数,它放宽了对值的要求,并且权重属于同一类型.即,我想支持说:(value:Float,weight:Int)(value:Int,weight:Float)参数的序列,而不仅仅是:(value:Int,weight:Int).[请参阅我之前提到的问题.]

这就是我目前拥有的:

def weightedSum[A: Numeric](weightedValues: GenSeq[(A, A)]): (A, A)

def weightedAverage[A: Numeric](weightedValues: GenSeq[(A, A)]): A = {
    val (weightSum, weightedValueSum) = weightedSum(weightedValues)
    implicitly[Numeric[A]] match {
        case num: Fractional[A] => ...
        case num: Integral[A] => ...
        case _ => sys.error("Undivisable numeric!")
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我喂它,例如:

val values:Seq[(Float,Float)] = List((1,2f),(1,3f))
val avg= weightedAverage(values)
Run Code Online (Sandbox Code Playgroud)

但是,如果我不"重叠"权重IntFloat:

val values= List((1,2f),(1,3f)) //scalac sees it as Seq[(Int,Float)] 
val avg= weightedAverage(values)
Run Code Online (Sandbox Code Playgroud)

Scala编译器会告诉我:

错误:无法找到Numeric类型的证据参数的隐含值[AnyVal]
val avg = weightedAverage(values)

有办法绕过这个吗?

我试图编写一个NumericCombine我参数化的类,A并将B这些类型"组合"成"通用"类型AB(例如,组合FloatInt给你Float):

abstract class NumericCombine[A: Numeric, B: Numeric] {
    type AB <: AnyVal

    def fromA(x: A): AB
    def fromB(y: B): AB
    val num: Numeric[AB]

    def plus(x: A, y: B): AB = num.plus(fromA(x), fromB(y))
    def minus(x: A, y: B): AB = num.minus(fromA(x), fromB(y))
    def times(x: A, y: B): AB = num.times(fromA(x), fromB(y))
}
Run Code Online (Sandbox Code Playgroud)

我设法用类型类型模式编写基于此的简单timesplus函数,但由于NumericCombine引入了路径依赖类型AB,"组合"类型证明比我预期的更难.请查看问题以获取更多信息,并在此处查看完整实施NumericCombine.

更新

作为另一个问题(这里的完整工作演示)的答案,已经获得了一个令人满意的解决方案,但是考虑到与@ziggystar 讨论中提出的观点,仍然存在一些设计改进的空间.

zig*_*tar 4

线性组合

T我认为涉及通过类型标量来衡量/缩放某些类型元素的更一般任务S是线性组合。以下是某些任务的权重限制:

因此,根据这种分类,最常见的情况是线性组合。根据维基百科,它要求权重S为一个域,并在 上T形成一个向量空间S

编辑:您对类型的真正最普遍的要求是在环上T形成一个模块(wiki)S,或者T成为一个S模块。

尖塔

您可以使用类型类来设置这些要求。还有spire,它已经有Field和的类型类VectorSpace。我自己没用过,所以你必须自己检查一下。

Float/Int不起作用

从这个讨论中还可以明显看出,以及您已经观察到的事实是,作为Float权重和Int元素类型是行不通的,因为整数不会在实数上形成向量空间。你必须Int首先晋升Float

通过类型类进行提升

标量类型只有两个主要候选者,即FloatDouble。主要只是Int晋升的候选者,因此您可以执行以下操作作为简单且不那么通用的解决方案:

case class Promotable[R,T](promote: R => T)

object Promotable {
  implicit val intToFloat = Promotable[Int,Float](_.toFloat)
  implicit val floatToDouble = Promotable[Float,Double](_.toDouble)
  implicit val intToDouble = Promotable[Int,Double](_.toDouble)

  implicit def identityInst[A] = Promotable[A,A](identity)
}As a "small" solution you could write a typeclass 

def weightedAverage[S,VS](values: Seq[(S,VS)])(implicit p: Promotable[VS,S]) = ???
Run Code Online (Sandbox Code Playgroud)