Можно ли использовать импликации для устранения неоднозначности перегруженного определения? - PullRequest
3 голосов
/ 23 сентября 2019

Рассмотрим следующее перегруженное определение метода 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
}

Мне бы хотелось, чтобы второе определение, которое возвращает 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] недоступно для устранения неоднозначности двух перегрузок?

Ответы [ 2 ]

2 голосов
/ 23 сентября 2019

Scala рассматривает только первый список аргументов для разрешения перегрузки в соответствии со спецификацией .Оба mean метода считаются одинаково конкретными и неоднозначными.

Но для неявного разрешения также учитываются последствия в области видимости, поэтому обходным путем может быть использование шаблона магнита или класса типов.Вот пример использования шаблона магнита, который, я считаю, проще:

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   
1 голос
/ 23 сентября 2019

Вот моя попытка решения класса типов, предложенная другими

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...