Scala <collection>. Уменьшает странное поведение на универсальных типах - PullRequest
0 голосов
/ 27 декабря 2018

Мне было интересно, почему этот кусок кода не скомпилируется?

Есть ли в Scala способ создания метода / функции, который является параметризованным для общего использования и допускает такую ​​операцию, как «уменьшить».

Это поведение имеет что-то общее с стиранием типов или это что-то еще?Я хотел бы видеть широкое объяснение этого:)

def func2[B <: Int](data: Seq[B]): Unit = {
    val operation = (a: B, b: B) => a.-(b)

    data.reduce(operation)
  }

Компилятор говорит:

type mismatch;
 found   : (B, B) => Int
 required: (Int, Int) => Int

Кроме того, в том же духе - возможно ли в целом назвать любое «потоковое»метод, на параметризованный сбор с этим методом:

   def func2[B <: Int](data: Seq[B]): Unit = {
       val operation = (a: B, b: B) => a.-(b)

       data.sum
  }

также дает:

could not find implicit value for parameter num: Numeric[B]

Ответы [ 4 ]

0 голосов
/ 28 декабря 2018

Не совсем понятно, чего вы пытаетесь достичь.

Прежде всего, ограничение B <: Int не имеет смысла, поскольку Int - это класс final в Scala.

Во-вторых, использование reduce вместе с - также не имеет смысла, поскольку - не является коммутативным .Это важно, потому что reduce в отличие от reduceLeft / reduceRight или foldLeft / foldRight не гарантирует порядок оценки.На самом деле

def reduce[A1 >: A](op: (A1, A1) => A1): A1 = reduceLeft(op)

является такой же допустимой реализацией по умолчанию, как и

def reduce[A1 >: A](op: (A1, A1) => A1): A1 = reduceRight(op)

, но очевидно, что они будут давать разные результаты для операции -.

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

def product[B: Numeric](data: Seq[B]): B = {
  val numeric = implicitly[Numeric[B]]
  data.reduce(numeric.times)
}

Обратите внимание, что умножение является коммутативным, поэтому это разумная реализация.На самом деле это почти то, как sum и product реализованы в стандартной библиотеке.Основное отличие состоит в том, что реальная реализация использует foldLeft, что позволяет определить значение по умолчанию для пустого Seq (0 и 1 соответственно)

0 голосов
/ 27 декабря 2018

Результат a.-(b) всегда равен Int, а ваша функция operation равна (B, B) => Int.Но reduce ожидает функцию (B, B) => B.

def reduce[A1 >: A](op: (A1, A1) => A1): A1

Таким образом, функция (Int, Int) => Int является единственной опцией для компилятора, поскольку Int тип результата operation.

Этот вариант компилируется:

def func2[B <: Int](data: Seq[B]): Unit = {
    val operation = (a: Int, b: Int) => a.-(b)
    data.reduce(operation)
}

Numeric не является ковариантным.Его интерфейс Numeric[T].Hense Numeric[B] не является подклассом Numeric[Int] для B <: Int и не существует неявного Numeric[B].

0 голосов
/ 27 декабря 2018

Почему я не могу установить границы верхних типов для типа коллекции и предположить, что у типа B (с этим ограничением) есть только те методы, которые мне нужны?

Ваше предположениеправильный.Ваша верхняя граница B делает следующую компиляцию

val operation = (a: B, b: B) => a.-(b) 

, а также делает reduce доступным для Seq[B], потому что Seq является ковариантным.

Поскольку компилятор знает, что«B ISA Int», существует метод -.Тем не менее, он все еще собирается вернуть Int.Поскольку подпись + ограничивает тип возвращаемого значения Int

def +(x: Int): Int

Операция reduce может понимать только один тип.Так что если у вас есть

reduce[B](operation)

Он будет ожидать, что operation будет иметь тип (B,B) => B

И если у вас есть

reduce[Int](operation)

Он будет ожидать operation быть типа (Int,Int) => Int

Одна из вещей, которые вы можете сделать, это

val operation = (a: Int, b: Int) => a - b

Это безопасно, потому что ваш B всегда также Int

0 голосов
/ 27 декабря 2018

это работает

def func2[B](data: Seq[B], f: (B, B) => B): Unit = {
  val operation = (a: B, b: B) => f(a, b)
  data.reduce(operation)
}
...