Reputation: 116
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
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
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
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
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