Scala <collection>。减少泛型类型的奇怪行为

时间:2018-12-27 12:31:03

标签: scala sum reduce

我一直在想,为什么这段代码无法编译?

Scala中是否有一种方法可以创建通用参数化的方法/函数,并允许类似“ reduce”的操作。

此行为与类型擦除有什么共同之处吗?还是其他?我希望看到对此的广泛解释:)

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 :(得分:2)

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]的{​​{1}}的子类,并且没有隐式的B <: Int

答案 1 :(得分:2)

  

为什么我不能对集合的类型设置上限,并假设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

答案 2 :(得分:0)

this works

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

答案 3 :(得分:0)

目前尚不清楚您要实现的目标。

首先限制B <: Int毫无意义,因为Int是Scala中的final类。

第二,将reduce-一起使用也没有意义,因为-不是commutative。这很重要,因为reducereduceLeft / reduceRightfoldLeft / foldRight不同,它不能保证评估的顺序。其实

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

是有效的默认实现
def reduce[A1 >: A](op: (A1, A1) => A1): A1 = reduceRight(op)

但是很明显,它们对于-操作将产生不同的结果。

从更高的角度来看,看起来可以使用type classes尤其是Numeric来完成与您想要实现的目标类似的事情。例如,您可以使用如下方法:

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

请注意,乘法是可交换的,因此这是一个合理的实现。实际上,这几乎是在标准库中实现sumproduct的方式。主要区别在于实际的实现使用foldLeft,它允许为空的Seq(分别为0和1)定义默认值

相关问题