Search code examples
scalamatrixtypestype-parameterf-bounded-polymorphism

why scala compiler says type arguments does not conform to bounds?


I created Combiner trait with subclasses Complex and IntCombiner and my objective is to make Matrix work with both Complex and Int. But some reason it dosen't compile saying that

[com.implicits.TestImplicits1.IntCombiner] do not conform to class Matrix's type parameter bounds [T <: com.implicits.TestImplicits1.Combiner[T]]
    val m1 = new Matrix[IntCombiner](3, 3)((1 to 9).sliding(3).map {

But as my understanding goes as IntContainer is the subclass of Combiner it should work. Why such an error please explain ?

object TestImplicits1 {

  trait Combiner[T] {

    def +(b: T): T

    def *(b: T): T

  }

  class Complex(r: Double, i: Double) extends Combiner[Complex] {

    val real = r

    val im = i

    override def +(b: Complex): Complex = {
      new Complex(real + b.real, im + b.im)
    }

    override def *(b: Complex): Complex = {
      new Complex((real * b.real) - (im * b.im), real * b.im + b.real * im)
    }

  }

  class IntCombiner(a: Int) extends Combiner[Int] {
    val v = a

    override def *(b: Int): Int = v * b

    override def +(b: Int): Int = v + b

  }

  class Matrix[T <: Combiner[T]](x1: Int, y1: Int)(ma: Seq[Seq[T]]) {

    self =>
    val x: Int = x1
    val y: Int = y1

    def dot(v1: Seq[T], v2: Seq[T]): T = {
      v1.zip(v2).map { t: (T, T) => {
        t._1 * t._2
      }
      }.reduce(_ + _)
    }

  }

  object MatrixInt extends App {
    def apply[T <: Combiner[T]](x1: Int, y1: Int)(s: Seq[Seq[T]]) = {
      new Matrix[T](x1, y1)(s)
    }

    val m1 = new Matrix[IntCombiner](3, 3)((1 to 9).sliding(3).map {
      x => x map { y => new IntCombiner(y) }
    }.toSeq)
  }

}

Solution

  • F-bounded polymorphism cannot be added to an existing Int class, because Int is just what it is, it does not know anything about your Combiner traits, so it cannot extend Combiner[Int]. You could wrap every Int into something like an IntWrapper <: Combiner[IntWrapper], but this would waste quite a bit of memory, and library design around F-bounded polymorphism tends to be tricky.

    Here is a proposal based on ad-hoc polymorphism and typeclasses instead:

    object TestImplicits1 {
    
      trait Combiner[T] {
        def +(a: T, b: T): T
        def *(a: T, b: T): T
      }
    
      object syntax {
        object combiner {
          implicit class CombinerOps[A](a: A) {
            def +(b: A)(implicit comb: Combiner[A]) = comb.+(a, b)
            def *(b: A)(implicit comb: Combiner[A]) = comb.*(a, b)
          }
        }
      }
    
      case class Complex(re: Double, im: Double)
    
      implicit val complexCombiner: Combiner[Complex] = new Combiner[Complex] {
    
        override def +(a: Complex, b: Complex): Complex = {
          Complex(a.re + b.re, a.im + b.im)
        }
    
        override def *(a: Complex, b: Complex): Complex = {
          Complex((a.re * b.re) - (a.im * b.im), a.re * b.im + b.re * a.im)
        }
    
      }
    
      implicit val intCombiner: Combiner[Int] = new Combiner[Int] {
        override def *(a: Int, b: Int): Int = a * b
        override def +(a: Int, b: Int): Int = a + b
      }
    
      class Matrix[T: Combiner](entries: Vector[Vector[T]]) {
        def frobeniusNormSq: T = {
          import syntax.combiner._
          entries.map(_.map(x => x * x).reduce(_ + _)).reduce(_ + _)
        }
      }
    }    
    

    I don't know what you attempted with dot there, your x1, x2 and ma seemed to be completely unused, so I added a simple square-of-Frobenius-norm example instead, just to show how the typeclasses and the syntactic sugar for operators work together. Please don't expect anything remotely resembling "high performance" from it - JVM traditionally never cared about rectangular arrays and number crunching (at least not on a single compute node; Spark & Co is a different story). At least your code won't be automatically transpiled to optimized CUDA code, that's for sure.