如何解决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]
的实例,因为AnyVal
是Int
的超类。 / 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]
如果SomeThing
在T
上是不变的(没有+
或-
),则实例将没有方差关系。
如果SomeThing
与T
([+T]
)是协变的,则实例将具有与A
和B
相同的方差关系。换句话说,如果A
是B
的子类型(反之亦然),则实例将反映相同的关系。
如果SomeThing
与T
([-T]
)是相反的,则实例将具有与A
和B
相反的变化关系。换句话说,如果A
是B
的子类型,那么instanceB
将是instanceA
的子类型。
但是差异指示器确实影响类型参数的使用方式。如果T
被标记为+
,则不能将其放置在一个协变位置;同样,如果标记为-
,则也不能将其放置在协变位置。在定义方法时,我们最经常遇到这种情况。
Scala方法与Scala函数特征密切相关:Function0
,Function1
,Function2
等。
考虑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 = ???
因此,总而言之,如果SomeThing
在T
上是协变的,那么您就不能将T
作为成员方法的传递参数,因为FunctionX
在其参数类型。同样,如果SomeThing
与T
是相反的,则您不能将T
作为成员方法的返回类型,因为FunctionX
的返回类型是协变的。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。