Dan Li
Dan Li

Reputation: 928

Scala: Why does the following give a type mismatch error?

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

Answers (3)

Andrey Tyukin
Andrey Tyukin

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

Gabriele Petronella
Gabriele Petronella

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

G&#225;bor Bakos
G&#225;bor Bakos

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

Related Questions