Error: Covariant type A occurs in contravariant po

2020-06-16 08:28发布

问题:

I was trying to write an immutable Matrix[A] class. I want the class to be covariant on A but when I put + in front of A compiler starts complaining about some operations in the class.

Following is a relevant subset of my Matrix class (The actual class is some 5 times bigger than the following subset):

class Matrix[+A] private(val contents: Vector[Vector[A]])(implicit numericEv: Numeric[A])
    extends ((Int, Int) => A) with Proxy {

  import numericEv._
  import Prelude._

  // delegate `equals` and `hashCode` implementations to `contents`
  override def self = contents

  val nRows: Int = contents.length

  val nColumns: Int = contents(0).length.ensuring { len =>
    contents.forall(_.length == len)        
  }

  def dimensions = (nRows, nColumns)

  def isSquare = nRows == nColumns

  def hasSameOrderAs[B : Numeric](that: Matrix[B]) = this.dimensions == that.dimensions

  def isComformableWith[B : Numeric](that: Matrix[B]) = this.nColumns == that.nRows

  private def assertSameOrder[B : Numeric](that: Matrix[B]) {
    assert(this.hasSameOrderAs(that), "Matrices differ in dimensions.")
  }

  private def assertIsSquare() {
    assert(this.isSquare, "Not a square matrix.")
  }      

  def zipWith[B : Numeric, C : Numeric](that: Matrix[B])(f: (A, B) => C): Matrix[C] = {
    assertSameOrder(that)
    val zippedContents = (contents, that.contents).zipped.map((v1, v2) => (v1, v2).zipped.map(f))
    Matrix(zippedContents)
  }

  def map[B : Numeric](f: A => B): Matrix[B] = {
    Matrix(contents.map(_.map(f)))
  }

  def transpose: Matrix[A] = {
    assertIsSquare()
    Matrix(contents.transpose)
  }

  def +(that: Matrix[A]): Matrix[A] = this.zipWith(that)(_ + _)

  def -(that: Matrix[A]): Matrix[A] = this.zipWith(that)(_ - _)

  def *(scalar: A): Matrix[A] = this.map(_ * scalar)

  def *(that: Matrix[A]): Matrix[A] = {
    assert(this.isComformableWith(that))
    Matrix.tabulate(this.nRows, that.nColumns) { (r, c) =>
      (this(r), that.transpose(c)).zipped.map(_ * _).sum
    }
  }
}

object Matrix {
  def apply[A : Numeric](rows: Vector[A]*): Matrix[A] = Matrix(Vector(rows: _*))

  def apply[A : Numeric](contents: Vector[Vector[A]]): Matrix[A] = new Matrix(contents)

  def tabulate[A : Numeric](nRows: Int, nColumns: Int)(f: (Int, Int) => A): Matrix[A] = {
    Matrix(Vector.tabulate(nRows, nColumns)(f))
  }
}

Compiler shows the error "Covariant type A occurs in contravariant position" for the last four operations in the class. I am unable to understand the reason for these errors, and how to get rid of it. Please explain the reason behind these errors and suggest a way to work around them. Thanks.

回答1:

The reason for these errors is that it is not type-safe to declare it as you are doing. For instance, it would be possible to do this, otherwise:

class A(val x: Int)
class B(x: Int, val y: Int) extends A(x)

object NA extends Numeric[A] {
    def toDouble(x: A): Double = x.x.toDouble
    def toFloat(x: A): Float = x.x.toFloat
    def toLong(x: A): Long = x.x.toLong
    def toInt(x: A): Int = x.x
    def fromInt(x: Int): A = new A(x)
    def negate(x: A): A = new A(-x.x)
    def times(x: A,y: A): A = new A(x.x * y.x)
    def minus(x: A,y: A): A = new A(x.x - y.x)
    def plus(x: A,y: A): A = new A(x.x + y.x)
    def compare(x: A,y: A): Int = implicitly[Numeric[Int]].compare(x.x, y.x)
}

object NB extends Numeric[B] {
    def toDouble(x: B): Double = x.x.toDouble / x.y.toDouble
    def toFloat(x: B): Float = x.x.toFloat / x.y.toFloat
    def toLong(x: B): Long = (x.x / x.y).toLong
    def toInt(x: B): Int = x.x / x.y
    def fromInt(x: Int): B = new B(x, 1)
    def negate(x: B): B = new B(-x.x, x.y)
    def times(x: B,y: B): B = new B(x.x * y.x, x.y * y.y)
    def minus(x: B,y: B): B = new B(x.x * y.y - y.x * x.y, x.y * y.y)
    def plus(x: B,y: B): B = new B(x.x * y.y + y.x * x.y, x.y * y.y)
    def compare(x: B,y: B): Int = implicitly[Numeric[Int]].compare(x.x * x.y, y.x * y.y)
}

val mb = Matrix.tabulate(10, 10)((x, y) => new B(x, y))
def f(m: Matrix[A]) = {
  val ma = Matrix.tabulate(m.nRows, m.nColumns)((x, y) => 1)
  m + ma
}
f(mb)

Note that m + ma could not work, since m.+ expects an object of type B. If Scala allowed you to write it the way you did, though, this would be allowed.

The common way to avoid this problem is to write the method like this:

def +[B >: A](that: Matrix[B])(implicit num: Numeric[B]): Matrix[B] = this.zipWith(that)(B.plus)