1

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

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

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

同样,当我们有逆变类型 T (-T) 时,它不能作为方法参数传递;但这是允许的。为什么我认为它不能被传递是:例如,说方法调用 T 中存在的 a 上的方法(存在于对象 a 中)当我们传递 T 的超类型时,它可能不存在。但它是编译器允许的。这让我很困惑。

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

因此,通过查看上述内容,在方法参数和返回类型中应该允许协变。不能应用逆变。

有人可以帮我理解。

4

3 回答 3

1

我认为你是在反击这个问题。如果是协变的,则不能a:T作为方法的参数这一事实T是一个约束,因为否则某些不合逻辑的代码将完全有效

class A
class B extends A
class C extends B

val myBThing = new SomeThing[B]

在这里,myBThing.method接受 a 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] <: Something[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!

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

于 2020-11-11T07:08:10.380 回答
1

关于方差的关键在于它会影响类从外部的外观。

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

在这种情况下,您的方法

def method(a: Int)

会成为

def method(a: AnyVal)

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

逆变SomeThing[AnyVal]可以这样SomeThing[Int]对待

def method(a: AnyVal)

变成

def method(a: Int)

这没关系,因为您始终可以传递需要的Int位置AnyVal

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

于 2020-11-11T07:07:52.537 回答
1

在 的情况下,在实际影响类本身而不是类型参数之前class SomeThing[T]放置一个+或。-T

考虑以下:

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

如果在(no或)SomeThing上是不变的,则实例将没有方差关系。T+-

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

如果在( )SomeThing上是逆变的,则实例将具有与和具有相反的方差关系。换句话说, if是 的子类型then将是 的子类型。T[-T]ABABinstanceBinstanceA

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

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

考虑 的定义Function1

trait Function1[-T1, +R] extends AnyRef

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

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

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

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

因此,总而言之,如果SomeThing是协变的,T那么您不能将其T作为成员方法的传递参数,因为FunctionX它的参数类型是逆变的。同样, ifSomeThing是逆变T的,您不能将其T作为成员方法返回类型,因为FunctionX它的返回类型是协变的。

于 2020-11-11T07:18:34.700 回答