Search code examples
scalaimplicitoverload-resolution

Can implicits be used to disambiguate overloaded definition?


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?


Solution

  • 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