Scala中的协方差和反方差

问题描述

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

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

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

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

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

因此,通过查看以上内容,应该在方法参数以及返回类型中允许使用协变。不能应用相反变量。

有人可以帮我理解吗?

解决方法

差异的关键在于,它会影响类从外部的外观

协方差说,SomeThing[Int]的实例可以被视为SomeThing[AnyVal]的实例,因为AnyValInt的超类。 / p>

在这种情况下,您的方法

def method(a: Int)

将成为

def method(a: AnyVal)

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

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

def method(a: AnyVal)

成为

def method(a: Int)

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

如果您遵循返回类型的逻辑,您会发现它反过来起作用。可以返回协变类型,因为它们总是可以被视为超类类型。您不能返回协变类型,因为返回类型可能是实际类型的子类型,无法保证。

,

我认为您正在反击这个问题。如果a:T是协变的,则不能将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]

假设您要定义以下内容

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!

有关更多详细信息,请参见this answer

,

对于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的返回类型是协变的。