可以使用隐式来消除重载定义的歧义吗?

Can implicits be used to disambiguate overloaded definition?

考虑以下方法的重载定义 mean:

def mean[T](data: Iterable[T])(implicit number: Fractional[T]): T = {
  import number._
  val sum = data.foldLeft(zero)(plus)
  div(sum, fromInt(data.size))
}

def mean[T](data: Iterable[T])(implicit number: Integral[T]): Double = {
  import number._
  val sum = data.foldLeft(zero)(plus)
  sum.toDouble / data.size
}

我想要第二个定义 returns Double 仅用于 Integral 类型的情况,但是

mean(List(1,2,3,4))

导致编译错误

Error: ambiguous reference to overloaded definition,
both method mean in class A$A16 of type [T](data: Iterable[T])(implicit number: Integral[T])Double
and  method mean in class A$A16 of type [T](data: Iterable[T])(implicit number: Fractional[T])T
match argument types (List[Int])
mean(List(1,2,3,4))
^

有没有什么方法可以利用 Fractional[Int] implicit 不可用这一事实来消除两个重载的歧义?

Scala 只考虑 first argument list for the overload resolution, according to the specificationmean 两种方法都被认为是同样具体和模棱两可的。

但是对于隐式解析,还考虑了作用域中的隐式,因此解决方法是使用磁体模式或类型 class。这是一个使用磁铁图案的例子,我认为它更简单:

def mean[T](data: MeanMagnet[T]): data.Out = data.mean

sealed trait MeanMagnet[T] {
  type Out
  def mean: Out
}

object MeanMagnet {
  import language.implicitConversions

  type Aux[T, O] = MeanMagnet[T] { type Out = O }

  implicit def fromFractional[T](
    data: Iterable[T]
  )(
    implicit number: Fractional[T]
  ): MeanMagnet.Aux[T, T] = new MeanMagnet[T] {
    override type Out = T

    override def mean: Out = {
      import number._
      val sum = data.foldLeft(zero)(plus)
      div(sum, fromInt(data.size))
    }
  }

  implicit def fromIntegral[T](
    data: Iterable[T]
  )(
    implicit number: Integral[T]
  ): MeanMagnet.Aux[T, Double] = new MeanMagnet[T] {
    override type Out = Double

    override def mean: Out = {
      import number._
      val sum = data.foldLeft(zero)(plus)
      sum.toDouble / data.size
    }
  }
}

使用这个定义它可以正常工作:

scala> mean(List(1,2,3,4))
res0: Double = 2.5

scala> mean(List(1.0, 2.0, 3.0, 4.0))
res1: Double = 2.5

scala> mean(List(1.0f, 2.0f, 3.0f, 4.0f))
res2: Float = 2.5   

这是我根据其他人的建议对类型类解决方案的尝试

trait Mean[In, Out] {
  def apply(xs: Iterable[In]): Out
}

object Mean {
  def mean[In, Out](xs: Iterable[In])(implicit ev: Mean[In, Out]): Out = ev(xs)

  private def meanFractional[T](data: Iterable[T])(implicit number: Fractional[T]): T = {
    import number._
    val sum = data.foldLeft(zero)(plus)
    div(sum, fromInt(data.size))
  }

  private def meanIntegral[T](data: Iterable[T])(implicit number: Integral[T]): Double = {
    import number._
    val sum = data.foldLeft(zero)(plus)
    sum.toDouble / data.size
  }

  implicit val meanBigInt: Mean[BigInt, Double] = meanIntegral _
  implicit val meanInt: Mean[Int, Double] = meanIntegral _
  implicit val meanShort: Mean[Short, Double] = meanIntegral _
  implicit val meanByte: Mean[Byte, Double] = meanIntegral _
  implicit val meanChar: Mean[Char, Double] = meanIntegral _
  implicit val meanLong: Mean[Long, Double] = meanIntegral _
  implicit val meanFloat: Mean[Float, Float] = meanFractional _
  implicit val meanDouble: Mean[Double, Double] = meanFractional _
  import scala.math.BigDecimal
  implicit val meanBigDecimal: Mean[BigDecimal, BigDecimal] = meanFractional _
}

object MeanTypeclassExample extends App {
  import Mean._
  println(mean(List(1,2,3,4)))
  println(mean(List(1d,2d,3d,4d)))
  println(mean(List(1f,2f,3f,4f)))
}

输出

2.5
2.5
2.5