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 typeSeq[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.