Search code examples
scalagenericstype-conversiongeneric-programminggeneric-collections

Why can't I convert this type into a generic one?


def linearInterpolation(weights: Seq[Double], points: Seq[Seq[Double]]) : Seq[T] = {

  weights.zip(points).map(
    weight_point => weight_point._2.map(coordinate => weight_point._1 * coordinate)
  ).reduce((point_a : Seq[Double], point_b : Seq[Double]) => point_a.zip(point_b).map(coordinate_points => (coordinate_points._1 + coordinate_points._2).asInstanceOf[T]))

}

In this code, I'm trying to convert the type of the return, a Seq[Double], to a Seq[T]. At the execution of the call, T could be e.g. Double or Int.

This conversion should be realized thanks to .asInstanceOf[T].

Errors

The code compiles.

But if I execute it, I get the following errors :

Error:(25, 61) type mismatch; found : (Seq[Double], Seq[Double]) => Seq[T] required: (Seq[Any], Seq[Any]) => Seq[Any] ).reduce((point_a : Seq[Double], point_b : Seq[Double]) => point_a.zip(point_b).map(coordinate_points => (coordinate_points._1 + coordinate_points._2).asInstanceOf[T]))

Error:(25, 13) type mismatch; found : Seq[Any] required: Seq[T] ).reduce((point_a : Seq[Double], point_b : Seq[Double]) => point_a.zip(point_b).map(coordinate_points => (coordinate_points._1 + coordinate_points._2).asInstanceOf[T]))

Question

Why does the execution fail ? How to achieve this conversion from a Seq[Double] to a Seq[T] ?


Solution

  • First, no, this code doesn't compile: those are compilation errors, not runtime exceptions. You can see because it starts with Error:, points to an exact position in source code (exceptions only have a line number), and there's no stack trace.

    Now, if it did compile, it wouldn't work, but that's a separate issue.

    So why doesn't it compile? The type of weights.zip(points).map(...) is Seq[Seq[Double]], so the signature of reduce becomes reduce[A1 >: Seq[Double]](op: (A1, A1) => A1): A1. Note that the return and argument types of reduce's argument must match, and in your case they don't (you have (Seq[Double], Seq[Double]) => Seq[T]). By itself that would be enough not to compile.

    The expected type of the entire weights.zip(points).map(...).reduce(...) is Seq[T], so the compiler needs to pick A1 which is:

    1. a supertype of Seq[Double] to satisfy the constraint

    2. a subtype of Seq[T] to make return types match

    Such a type doesn't exist (without additional constraints on T), but if it did it would be Seq[SomeType], and that's as far as the compiler should get. Why it ends up showing Any, I really don't see.

    How to achieve this conversion from a Seq[Double] to a Seq[T] ?

    It makes more sense if you have weights: Seq[T], points: Seq[Seq[T]]. In that case, use Numeric. There's quite a few answers on Stack Overflow and outside explaining how, e.g. Scala equivalent of Java's Number.

    For weights: Seq[Double], points: Seq[Seq[Double]], I would just add a function Double => T as an extra argument:

    def linearInterpolation(weights: Seq[Double], points: Seq[Seq[Double]])(fromDouble: Double => T) : Seq[T] = {
    
      weights.zip(points).map(
        weight_point => weight_point._2.map(coordinate => weight_point._1 * coordinate)
      ).reduce((point_a : Seq[Double], point_b : Seq[Double]) => point_a.zip(point_b).map(coordinate_points => coordinate_points._1 + coordinate_points._2)).map(fromDouble)
    
    }