Scala中的协方差和反方差



我在理解方法参数中限制的协方差类型时感到困惑。我通读了很多材料,但我无法理解以下概念。

class SomeThing[+T] {
def method(a:T) = {...}   <-- produces error
}

在上面的代码中,a是T类型。为什么我们不能传递T的子类型?方法对参数x的所有期望,都可以由T的子类型完美地实现。

类似地,当我们有反变量类型T(-T)时,它不能作为方法参数传递;但这是允许的。我认为它不能被传递的原因是:例如,say方法调用一个方法(存在于对象a中)在上存在于T中的。当我们通过T的超类型时,它可能不存在。但它是编译器允许的。这让我很困惑。

class SomeThing[-T] {
def method(a:T) = {...}   <-- allowed
}

因此,通过查看上面的内容,它是协变的,应该在方法参数和返回类型中都允许。无法应用反变体。

有人能帮我理解吗。

方差的关键是它会影响类从外部看的样子。

协方差表示SomeThing[Int]的实例可以被视为SomeThing[AnyVal]的实例,因为AnyValInt的超类。

在这种情况下,您的方法

def method(a: Int)

将成为

def method(a: AnyVal)

这显然是一个问题,因为您现在可以将Double传递给只接受Int值的SomeThing[Int]方法。请记住,实际对象不会改变,只会改变类型系统感知它的方式。

相反SomeThing[AnyVal]可以被视为SomeThing[Int],所以

def method(a: AnyVal)

成为

def method(a: Int)

这是可以的,因为您总是可以在需要AnyVal的地方传递Int

如果你遵循返回类型的逻辑,你会发现它的工作方式正好相反。返回协变类型是可以的,因为它们总是可以被视为超类类型。不能返回逆变类型,因为返回类型可能是实际类型的子类型,这是无法保证的。

我认为您在向后攻击这个问题。如果T是协变的,则不能将a:T作为方法的自变量,这是一个约束,因为否则一些不合逻辑的代码将是完全有效的

class A
class B extends A
class C extends B
val myBThing = new SomeThing[B]

这里,myBThing.method接受一个B,您是对的,我们可以传递任何扩展B的内容,所以myBThing.method(new C)完全可以。然而,myBThing.method(new A)不是!

既然我们已经用协变定义了SomeThing,我也可以写这个

val myAThing: SomeThing[A] = myBThing // Valid since B <: A entails SomeThing[B] <: Something[A] by definition of covariance
myAThing.method(new A) // What? You're managing to send an A to a method that was implemented to receives B and subtypes!

现在,您可以看到为什么我们施加不将T作为参数传递的约束(参数处于"逆变位置")。

我们可以对收益头寸的反方差进行类似的论证。请记住,逆变意味着B <: A包含`SomeThing[A]<:某事[B]`。

假设您正在定义以下

class A
class B extends A
class SomeThingA[-T](val value: T) // Compiler won't like T in a return type like myThing.value
// If the class definition compiled, we could write
val myThingA: SomeThing[A] = new SomeThing(new A)
val someA: A = myThingA.value
val myThingB: SomeThing[B] = myThingA // Valid because T contravariant
val someB: B = myThingB.value // What? I only ever stored an A!

有关更多详细信息,请参阅此答案。

class SomeThing[T]的情况下,在T之前放置+-实际上对类本身的影响大于对类型参数的影响。

考虑以下内容:

val instanceA = new SomeThing[A]
val instanceB = new SomeThing[B]

如果SomeThingT上是不变的(没有+-),则实例将不具有方差关系。

如果SomeThingT([+T])上是协变的,则实例将具有与AB相同的方差关系。换句话说,如果AB的子类型(反之亦然),则实例将反映相同的关系。

如果SomeThingT([-T])相反,则实例将具有与AB相反的方差关系。换句话说,如果AB的子类型,则instanceB将是instanceA的子类型。

但是方差指标确实影响了类型参数的使用方式。如果T被标记为+,则它不能被放置在反变位置,同样,如果被标记为-,则它不能够被放置在协变位置。我们在定义方法时经常遇到这种情况。

Scala方法与Scala函数特性密切相关:Function0Function1Function2等。

考虑Function1:的定义

trait Function1[-T1, +R] extends AnyRef

现在假设您想要传递一个这种类型的函数。

def useThisFunc(f: A => B):Unit = {...}

因为Function1在其接收参数上是逆变的,在其结果上是协变的,所以以下所有参数都可以作为useThisFunc()参数接受。

val a2b       : A => B             = ???
val supa2b    : SuperOfA => B      = ???
val a2subb    : A => SubOfB        = ???
val supa2subb : SuperOfA => SubOfB = ???

因此,总之,如果SomeThingT上是协变的,那么就不能将T作为成员方法的传递参数,因为FunctionX在其参数类型上是逆变的。同样,如果SomeThingT相反,则不能将T作为成员方法返回类型,因为FunctionX的返回类型是协变的。

最新更新