Reputation: 928
I have the following code snippet:
class Statistics[T, U <: NumberLike[T]] {
def mean(xs: Vector[U]): U =
xs.reduce(_ + _) / xs.size
}
trait NumberLike[T] {
def + (that: NumberLike[T]): NumberLike[T]
def / (that: Int): NumberLike[T]
}
It compiles with the error:
error: type mismatch;
found : NumberLike[T]
required: U
xs.reduce(_ + _) / xs.size
^
one error found
I don't understand why the compiler does not accept this; I've already defined that U
should be a subclass of NumberLike[T]
.
Upvotes: 0
Views: 70
Reputation: 44957
The error message says exactly what went wrong: your mean
method promised to return U
, but instead it returns a NumberLike[T]
.
You could solve it by F-bounded polymorphism:
class Statistics[T, U <: NumberLike[T, U]] {
def mean(xs: Vector[U]): U =
xs.reduce(_ + _) / xs.size
}
trait NumberLike[T, Res <: NumberLike[T, Res]] {
def + (that: NumberLike[T, Res]): Res
def / (that: Int): Res
}
This works, because now NumberLike
carries around the precise return type of +
and /
. Instead of returning some underspecified NumberLike
, it now guarantees to return a Res
.
Since you don't seem to use the type T
anywhere, it is actually much clearer if you omit T
altogether:
class Statistics[U <: NumberLike[U]] {
def mean(xs: Vector[U]): U =
xs.reduce(_ + _) / xs.size
}
trait NumberLike[U <: NumberLike[U]] {
def + (that: NumberLike[U]): U
def / (that: Int): U
}
Notice that now +
returns an U
, not merely some unrelated NumberLike[T]
.
Actually, you should consider separating the functionality (addition, division) from the syntactic sugar of the operators. You can move the actual computations into a separate typeclass, and provide the +
, /
methods by an implicit class:
/* The operations */
trait NumberTypeclass[U] {
def add(a: U, b: U): U
def divideByInt(a: U, i: Int): U
}
/* The syntactic sugar */
implicit class NumberOps[U: NumberTypeclass](u: U) {
def +(other: U): U = implicitly[NumberTypeclass[U]].add(u, other)
def /(i: Int): U = implicitly[NumberTypeclass[U]].divideByInt(u, i)
}
/* Usage example */
class Statistics[U: NumberTypeclass] {
def mean(xs: Vector[U]): U = {
xs.reduce(_ + _) / xs.size
}
}
At the first glance, it is slightly more verbose, but it has the advantage that you can omit the complicated F-bounded polymorphism, and that you can provide different syntax styles for the same functionality. Typeclasses also tend to compose better than complex class hierarchies with F-bounded polymorphism.
Upvotes: 2
Reputation: 108159
The error is a bit misleading, but if you look closely the ^
is on the +
, meaning the return type of that operation is wrong.
The issue is with the fact that +
and /
are operations that return NumberLike[T]
, but you're requiring U
as the return type of mean
.
Since functions are covariant in their return type, you cannot return a NumberLike
where U
is expected.
With this in mind, you can probably work around this by making sure your NumberLike
operations return a more precise type:
trait NumberLike[T] {
def +[A <: NumberLike[T]](that: A): A
def /[A <: NumberLike[T]](that: Int): A
}
Now your definition of mean
should compile.
Upvotes: 1
Reputation: 9100
In reduce
it is expected that the binary operator has a uniform signature, in this case (U, U) => U
, although in your case that is (U, NumberLike[T]) => NumberLike[T]
.
This won't work even if you fixes the trait, because of the expected result type in mean
, +
, /
. You told it should be U
in mean
, while the +
, /
methods in NumberLike
return NumberLike[T]
values, not the subtype of it. (Scala does not support MyTypes, but this question might be interesting for you.)
Upvotes: 0