Написание общей функции в Scala

Я пытаюсь написать обобщенную среднюю функцию, которая работает с Iterable, которая содержит числовые типы. Он будет работать, скажем, на массивах, так:

val rand = new scala.util.Random()
val a = Array.fill(1000) { rand.nextInt(101) }
val b = Array.fill(1000) { rand.nextDouble }

println(mean(a))
println(mean(b))

и т.д., надеемся, что он сможет работать с другими итерами, такими как списки.

Я пробовал различные заклинания для среднего метода, безрезультатно:

def mean[T <% Numeric[T]](xs: Iterable[T]) = xs.sum.toDouble / xs.size
def mean[A](xs: Iterable[Numeric[A]]):Double = xs.sum.toDouble / xs.size
def mean[T](xs: Iterable[T])(implicit num: Numeric[T]):Double = xs.sum / xs.size
def mean(xs: Iterable[Double]) = xs.sum / xs.size

Каков правильный способ сделать это в Scala?

Ответ 1

Это работает:

def mean[T : Numeric](xs: Iterable[T]): T = implicitly[Numeric[T]] match {
    case num: Fractional[_] => import num._; xs.sum / fromInt(xs.size)
    case num: Integral[_] => import num._; xs.sum / fromInt(xs.size)
    case _ => sys.error("Undivisable numeric!")
}

Итак, давайте сделаем некоторые объяснения. Во-первых, Numeric должен использоваться в шаблоне типа. То есть вы не говорите, что тип T есть или может быть преобразован в Numeric. Вместо этого Numeric предоставляет методы по типу T. Одним из таких примеров является num.fromInt.

Далее, Numeric не предоставляет общий оператор деления. Вместо этого нужно выбирать между Fractional и Integral. Здесь я сопоставляю Numeric[T], чтобы различать оба.

Обратите внимание, что я не использую T в матче, потому что Scala не может проверять параметры типа на совпадения, поскольку они стираются. Вместо этого я использую _, а Scala указывает правильный тип, если это возможно (как здесь).

После этого я импортирую num._, где num либо Fractional, либо Integral. Это приводит к некоторым неявным преобразованиям в контекст, который позволяет мне делать такие вещи, как вызов метода / напрямую. Если бы я не делал этого импорта, я был бы вынужден написать это:

num.div(xs.sum, num.fromInt(xs.size))

Обратите внимание, что мне не нужно передавать неявный параметр в xs.sum, так как он уже неявно доступен в области.

Я предполагаю, что это так. Я что-то пропустил?

Ответ 2

Одна из ваших версий довольно близка:

def mean[T](xs: Iterable[T])(implicit num: Numeric[T]):Double = 
  num.toDouble(xs.sum) / xs.size

Вот другой синтаксис:

def mean[T: Numeric](xs: Iterable[T]):Double =
  implicitly[Numeric[T]].toDouble(xs.sum) / xs.size

Ответ 3

def mean[A](it:Iterable[A])(implicit n:Numeric[A]) = {
  it.map(n.toDouble).sum / it.size
}

Ответ 4

Это довольно старый вопрос, но я в основном делаю это

def average[A](list: List[Any])(implicit numerics: Numeric[A]): Double = {
  list.map(Option(_)).filter(_.isDefined).flatten match {
    case Nil => 0.0
    case definedElements => numerics.toDouble(list.map(_.asInstanceOf[A]).sum) / definedElements.length.toDouble
  }
}

для списка, который может содержать значения null (я должен поддерживать совместимость с Java). Элементы null не учитываются в среднем.