Consider the following overloaded definition of method 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
}
I would like second definition which returns Double
only to be used in the case of Integral
types, however
mean(List(1,2,3,4))
results in compiler error
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))
^
Is there any way to use the fact that Fractional[Int]
implicit is not available in order to disambiguate the two overloads?
Scala only considers the first argument list for the overload resolution, according to the specification. Both mean
methods are deemed equally specific and ambiguous.
But for implicit resolution the implicits in scope are also considered, so a workaround could be to use a magnet pattern or a type class. Here is an example using the magnet pattern, which I believe is simpler:
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
}
}
}
With this definition it works normally:
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