Scala中的逆变与协方差

CSn*_*erd 11 scala covariance contravariance

我刚学会了Scala.现在我对Contravariance和Covariance很困惑.

从这个页面,我学到了以下内容:

协方差

也许子类型最明显的特征是能够用表达式中较窄类型的值替换较宽类型的值.例如,假设我有一些类型的Real,Integer <: Real和一些不相关的类型Boolean.我可以定义一个is_positive :: Real -> BooleanReal值进行操作的函数,但我也可以将此函数应用于类型Integer(或任何其他子类型Real)的值.用更窄(后代)类型替换更宽(祖先)类型被称为covariance.这个概念covariance允许我们编写通用代码,在推理面向对象编程语言中的继承和函数式语言中的多态时非常有用.

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

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)
Run Code Online (Sandbox Code Playgroud)

参数xfooIS contravariant; 它期望一个类型的参数List[Dog],但我们给它一个List[Beagle],这没关系

[我认为第二个例子也应该证明Covariance.因为从第一个例子开始,我学会了"将此函数应用于类型Integer(或任何其他子类型Real)的值".相应地,这里我们将此函数应用于类型List[Beagle](或任何其他子类型List[Dog])的值.但令我惊讶的是,第二个例子证明了Cotravariance]

我认为两个人说的是同一个东西,但有一个证明Covariance了,另一个证明了Contravariance.我也从SO那里看到了这个问题.不过我还是很困惑.我错过了什么或者其中一个例子是错的吗?

ste*_*tew 10

你可以将a传递List[Beagle]给一个期望a List[Dog]的函数与函数的逆变量无关,它仍然是因为List是协变的而且List[Beagle]是一个List[Dog].

相反,我们说你有一个功能:

def countDogsLegs(dogs: List[Dog], legCountFunction: Dog => Int): Int
Run Code Online (Sandbox Code Playgroud)

此功能计算狗列表中的所有腿.它需要一个接受狗的函数并返回一个int,表示这条狗有多少腿.

更进一步说我们有一个功能:

def countLegsOfAnyAnimal(a: Animal): Int
Run Code Online (Sandbox Code Playgroud)

可以计算任何动物的腿.我们可以将我们countLegsOfAnyAnimalcountDogsLegs函数作为函数参数传递给我们的函数,这是因为如果这个东西可以计算任何动物的腿,它可以计算狗的腿,因为狗是动物,这是因为函数是逆变的.

如果你看一下Function1(一个参数的函数)的定义,它就是

trait Function1[-A, +B]
Run Code Online (Sandbox Code Playgroud)

也就是说,它们的输入和输出的协变性是逆向的.所以,Function1[Animal,Int] <: Function1[Dog,Int]因为Dog <: Animal


Von*_*onC 9

最近有关该主题的文章(2016年8月)是Matt Handler 撰写的 " 逆变与协方差作弊代码 " .

它从" 主持人和访客的协方差与反演 "中提出的一般概念开始,以及Andre Tyukinanoopelias答案.

http://blog.originate.com/images/variance.png

最后得出的结论是:

以下是如何确定您是否type ParametricType[T]可以协变/逆变:

  • 当一个类型不调用它是泛型的类型的方法时,它可以是协变的.
    如果类型需要在传递给它的泛型对象上调用方法,则它不能是协变的.

原型示例:

Seq[+A], Option[+A], Future[+T]
Run Code Online (Sandbox Code Playgroud)
  • 当类型调用通用的类型时,类型可以是逆变的.
    如果类型需要返回它是泛型的类型的值,则它不能是逆变的.

原型示例:

`Function1[-T1, +R]`, `CanBuildFrom[-From, -Elem, +To]`, `OutputChannel[-Msg]`
Run Code Online (Sandbox Code Playgroud)

关于逆变,

函数是逆变的最好例子
(请注意,它们只是对其参数的逆变,并且它们实际上是对其结果的协变).
例如:

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
Run Code Online (Sandbox Code Playgroud)

我们应该weinerosity作为一个参数传递给isDogCuteEnough谁?答案是否定的,因为该函数isDogCuteEnough只能保证它能够最多地传递Dog给函数f.
当函数f期望某些东西比isDogCuteEnough可以提供的更具体时,它可能会尝试调用一些Dogs没有的方法(比如.weinernessa Greyhound,这是疯了).