简体   繁体   中英

Covariant type FParam occurs in contravariant position in type Seq[FParam] of value guesses

Consider this simple example:

trait Optimizer[+FParam, FRes] {
  def optimize(
    fn: (FParam) => FRes,
    guesses: Seq[FParam] // <--- error
  )
}

It doesn't compile, because

Covariant type FParam occurs in contravariant position in type Seq[FParam] of value guesses.

But seq is defined as trait Seq[+A] , so what is the source of this contravariance? ( Question 1 )

Conversely, consider this simple example with -FParam :

trait Optimizer[-FParam, FRes] {
  def optimize(
    fn: (FParam) => FRes, // <--- error
    guesses: Seq[FParam]
  )
}

Contravariant type occurs in covariant position in type (FParam) => FRes

Again, the same paradox: in Function1[-T1, R] , the first type parameter is clearly contravariant, so why is FParam in a covariant position? ( Question2 )

I can fix this issue by flipping the variance as described in Lower type bounds , but why it is necessary is unclear.

trait Optimizer[+FParam, FRes] {
  type U <: FParam

  def optimize(
    fn: (FParam) => FRes,
    guesses: Seq[U]
  )
}

Question 1 : +FParam means the covariant type FParam and it varies from supertype to subtype FParam . for the guesses it's expecting cotravariant type for Seq . So you can do it by explicitly stating a supertype of FPParam for this, like:

def optimize[B >: FParam](fn: (B) => FRes, guesses: Seq[B]) // B is the super type of FParam

or like the SeqViewLike .

so for why guesses it's expecting cotravariant type for Seq ? for example:

trait Animal

case class Dog() extends Animal

case class Cat() extends Animal
val o = new Optimizer2[Dog, Any]
val f: Dog => Any = (d: Dog) =>  ...
o.optimize(f, List(Dog(), Cat())) // if we don't bind B in `guesses` for supertype of `FParam`, it will fail in compile time, since the `guesses` it's expecting a `Dog` type, not the `Animal` type. when we bind it to the supertype of `Dog`, the compiler will infer it to `Animal` type, so `cotravariant type` for `Animal`, the `Cat` type is also matched.

Question 2 : -FParam means the cotravairant type FParam and it varies from supertype FParam to it's subtype .for fn: Function1[-T1, +R]//(FParam) => FRes it's expecting covariant type for this. So you can do it by inverse the Question 1 type state, like:

def optimize[B <: FParam](fn: (B) => FRes, guesses: Seq[B]) // B is the sub type of FParam

The problem is that FParam isn't used directly. It is in the argument of optimize , and as such its variance is flipped. To illustrate, let's look at the type of optimize (see val optim ):

trait Optimizer[+FParam, FRes] {
  type U <: FParam

  def optimize(
    fn: (FParam) => FRes,
    guesses: Seq[U]
  )

  val optim: Function2[
    Function1[
      FParam,
      FRes
    ],
    Seq[U],
    Unit
  ] = optimize
}

Having just worked through this problem hopefully I can cut through the confusion.

Seq[] are not lists and cannot have more than one class type at once inside them. Your trait Optimizer has a unknown but fixed class FParam or a subclass, but your function returning a Seq[FParam] is not bound to be the same subClass as the enclosing object. As such, there could end up a runtime with different subclasses ending up inside the Seq[FParam] at the same time, so the entire code block becomes a Scala compile error.

abstract class Optimizer[+FParam, FRes] {

}

object Optimizer{
   def optimize[FParam,FRes](obj: Optimizer[FParam,FRes], param : FParam) : (FParam,Seq[FParam]) = (param,(Nil)) 
}

The trait (now an abstract class) still has covariant generic parameters, but the function using that covariance generic in its parameter is type locked to an exact type with an invariant - mandatory the same class or subclass in the function now in a singleton object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM