Scala中的逆变换与协方差

Scala中的逆变换与协方差,scala,covariance,contravariance,Scala,Covariance,Contravariance,我刚学了Scala。现在我对逆变和协方差感到困惑 从中,我学到了以下几点: 协方差 也许子类型最明显的特征是能够在表达式中用较窄类型的值替换较宽类型的值。例如,假设我有一些类型Real,Integer Boolean对Real值进行操作,但我也可以将此函数应用于Integer类型的值(或Real的任何其他子类型)。用较窄的(后代)类型替换较宽的(祖先)类型称为协方差。协方差的概念允许我们编写泛型代码,并且在推理面向对象编程语言中的继承和函数语言中的多态性时非常有用 然而,我也从其他地方看到了一些

我刚学了Scala。现在我对逆变和协方差感到困惑

从中,我学到了以下几点:

协方差

也许子类型最明显的特征是能够在表达式中用较窄类型的值替换较宽类型的值。例如,假设我有一些类型
Real
Integer Boolean
Real
值进行操作,但我也可以将此函数应用于
Integer
类型的值(或
Real
的任何其他子类型)。用较窄的(后代)类型替换较宽的(祖先)类型称为协方差。协方差的概念允许我们编写泛型代码,并且在推理面向对象编程语言中的继承和函数语言中的多态性时非常有用

然而,我也从其他地方看到了一些东西:

scala> class Animal
    defined class Animal

scala> class Dog extends Animal
    defined class Dog

scala> class Beagle extends Dog
    defined class Beagle

scala> def foo(x: List[Dog]) = x
    foo: (x: List[Dog])List[Dog] // Given a List[Dog], just returns it
     

scala> val an: List[Animal] = foo(List(new Beagle))
    an: List[Animal] = List(Beagle@284a6c0)
foo
的参数
x
逆变型
;它需要一个
List[Dog]
类型的参数,但我们给它一个
List[Beagle]
,这没关系

[我认为第二个例子也应该证明协方差。因为从第一个例子中,我学到了“将此函数应用于
Integer
(或
Real
的任何其他子类型)类型的值”。因此,相应地,这里我们将此函数应用于
List[Beagle]
(或
列表[Dog]
的任何其他子类型)。但令我惊讶的是,第二个示例证明了
共变异


我想两个人说的是同一件事,但是一个证明了协方差,另一个证明了反方差。我也看到了。但是我仍然很困惑。我是不是遗漏了什么或者其中一个例子是错误的?

你可以将一个
列表[Beagle]
传递给一个需要
列表[Dog]的函数
与函数的逆变无关,这仍然是因为List是协变的,
List[Beagle]
是一个
List[Dog]

相反,假设您有一个函数:

def countDogsLegs(dogs: List[Dog], legCountFunction: Dog => Int): Int
def countLegsOfAnyAnimal(a: Animal): Int
此函数统计狗列表中的所有腿。它接受一个函数,该函数接受一只狗,并返回一个整数,表示该狗有多少条腿

此外,假设我们有一个函数:

def countDogsLegs(dogs: List[Dog], legCountFunction: Dog => Int): Int
def countLegsOfAnyAnimal(a: Animal): Int
这可以计算任何动物的腿。我们可以将任何动物的
countLegsOfAnyAnimal
函数传递给我们的
countDogsLegs
函数作为函数参数,这是因为如果这个东西可以计算任何动物的腿,它可以计算狗的腿,因为狗是动物,这是因为函数是逆变的

如果您查看
函数1
(一个参数的函数)的定义,它是

也就是说,它们的输入是逆变的,输出是协变的。因此,
Function1[Animal,Int]最近关于这个主题的一篇好文章(2016年8月)是

它从“”中介绍的一般概念以及和的图表开始

其结论是:

下面是如何确定您的
类型参数类型[T]
是否可以/不能是协变/逆变的:

  • 当类型不调用泛型类型上的方法时,它可以是协变的
    如果类型需要调用传递给它的泛型对象上的方法,那么它不能是协变的
原型示例:

  • 当某个类型调用泛型类型上的方法时,该类型可以是逆变的
    如果类型需要返回它所覆盖的泛型类型的值,那么它不能是逆变的
原型示例:

关于抵销

函数是逆变的最好例子
(请注意,它们在其论点上是唯一的逆变,在其结果上是协变的。
例如:

我们是否可以将
weineroosity
作为参数传递给
isdogCuteTough
?答案是否定的,因为函数
isdogCuteTough
只保证它可以将最具体的
Dog
传递给函数
f

当函数
f
需要比
isDogCuteen提供的更具体的东西时,它可以尝试调用一些
没有的方法(比如
灰狗
上的
.weineness
,这是疯狂的)


方差用于表示容器(例如:
List
)中的子类型。在大多数语言中,如果函数请求类Animal的对象,则传递继承
Animal
(例如:
Dog
)的任何类都是有效的。但是,对于容器,这些类不一定是有效的。 如果您的函数需要
Container[A]
,那么可以传递给它的可能值是什么?如果
B
扩展
A
并且传递
Container[B]
是有效的,那么它是协变的(例如:
列表[+T]
)。如果,A扩展B(反例)并传递
Container[B]
对于
容器[A]
是有效的,那么它是反变的。否则,它是不变的(这是默认值)。您可以参考一篇文章,其中我尝试解释了Scala中的差异

很好的解释。
groomAnyAnimal
的返回类型应该是
Dog
,因为函数的返回类型是协变的,而参数类型是逆变的。@stew那么你认为第二个示例的语句有什么错误吗?@CSnerd在第二个示例中,你不知怎么想做一个statem关于
x
是协变的还是逆变的,这是一个不确定的问题。然而,正如斯图所提到的,方差与变量无关
`Function1[-T1, +R]`, `CanBuildFrom[-From, -Elem, +To]`, `OutputChannel[-Msg]`
class Dachshund(
  name: String,
  likesFrisbees: Boolean,
  val weinerness: Double
) extends Dog(name, likesFrisbees)

def soundCuteness(animal: Animal): Double =
  -4.0/animal.sound.length

def weinerosity(dachshund: Dachshund): Double =
  dachshund.weinerness * 100.0

def isDogCuteEnough(dog: Dog, f: Dog => Double): Boolean =
  f(dog) >= 0.5