dreamsComeTrue
dreamsComeTrue

Reputation: 116

Scala <collection>.reduce strange behaviour on generic types

I've been wondering, why this piece of code won't compile?

Is there a way in Scala to create method/func that is generic parametrised and allows for such operation like 'reduce'.

Is this behaviour having anything in common with type erasure or is it something else? I would love to see broad explanation of this :)

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

    data.reduce(operation)
  }

Compiler says:

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

Also, in same spirit - is it possible overall to call any 'stream-like' method, on parametrized collection with this method:

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

       data.sum
  }

also gives:

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

Upvotes: 1

Views: 249

Answers (4)

SergGr
SergGr

Reputation: 23788

It is not really clear what you are trying to achieve.

First of all restriction B <: Int makes no sense as Int is a final class in Scala.

Secondly, using reduce together with - also makes no sense because - is not commutative. This is important because reduce unlike reduceLeft/reduceRight or foldLeft/foldRight does not guarantee the order of the evaluation. Actually

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

is as valid default implementation as

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

but obviously they will produce different results for - operation.

From a higher level point of view it looks like something similar to what you want to achieve can be done using type classes and particularly Numeric. For example you could have a method like this:

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

Note that multiplication is commutative so it is a reasonable implementation. Actually this is almost how sum and product are implemented in the standard library. The main difference is that the real implementation uses foldLeft which allows defining the default value for an empty Seq (0 and 1 respectively)

Upvotes: 0

Niks
Niks

Reputation: 4832

Why I can't put upper types bounds on type of collection, and assume, that type B (with that constraint) just has these methods I need?

Your assumption is correct. Your upper bound on B makes the following compile

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

And also makes reduce available on a Seq[B], because Seq is covariant.

Since compiler knows that "B ISA Int", the - method exists on it. However, it's still going to return an Int. Because the signature of + restricts the return type to Int

def +(x: Int): Int

The reduce operation can understand only one type. So if you have

reduce[B](operation)

It will expect operation to be of type (B,B) => B

And if you have

reduce[Int](operation)

It will expect operation to be of type (Int,Int) => Int

One of the things you can do is

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

This is safe because your B is always also an Int

Upvotes: 2

Aleksey Isachenkov
Aleksey Isachenkov

Reputation: 1240

The result of a.-(b) is always Int and your operation function is (B, B) => Int. But reduce expects a (B, B) => B function.

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

So an (Int, Int) => Int function is the only one option for the compiler because of Int result type of operation.

This variant compiles:

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

Numeric isn't covariant. Its interface is Numeric[T]. Hense Numeric[B] isn't subclass of Numeric[Int] for B <: Int and there is no implicit Numeric[B].

Upvotes: 2

gekomad
gekomad

Reputation: 565

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)
}

Upvotes: 0

Related Questions