Scala中的逆变和协方差

时间:2014-12-11 03:42:04

标签: scala covariance contravariance

我刚学会了Scala。现在我对Contravariance和Covariance感到困惑。

page开始,我学到了以下内容:

协方差

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

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

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)

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

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

我认为两个人说的是同一个问题,但有一个证明Covariance而另一个Contravariance。我也看到了this question from SO。不过我还是很困惑。我错过了什么或其中一个例子是错的吗?

3 个答案:

答案 0 :(得分:10)

您可以将List[Beagle]传递给期望List[Dog]的函数与函数的逆变无关,但仍然是因为List是协变的并且List[Beagle]List[Dog] 1}}。

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

def countDogsLegs(dogs: List[Dog], legCountFunction: Dog => Int): Int

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

此外,我们假设我们有一个功能:

def countLegsOfAnyAnimal(a: Animal): Int

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

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

trait Function1[-A, +B]

那就是他们的输入和输出的协变性是逆向的。因此Function1[Animal,Int] <: Function1[Dog,Int]

以来Dog <: Animal

答案 1 :(得分:8)

关于该主题的最新文章(2016年8月)是 Setting "checked" for a checkbox with jQuery? Cheat Codes for Contravariance and Covariance ”。

它从“ Matt Handler ”中提供的一般概念开始,以及Covariance and Contravariance of Hosts and VisitorsAndre Tyukinanoopelias中的图表。

answer

最后的结论是:

  

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

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

原型示例:

Seq[+A], Option[+A], Future[+T]
  
      
  • 当类型调用类型的方法时,类型可以是逆变的。
      如果类型需要返回它是泛型的类型的值,则它不能是逆变的。
  •   
     

原型示例:

`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
  

我们是否应该将weinerosity作为参数传递给isDogCuteEnough?答案是否定的,因为函数isDogCuteEnough仅保证它可以将Dog传递给函数f。   当函数f期望比isDogCuteEnough提供的更具体的内容时,它可能会尝试调用某些Dogs没有的方法(例如.weinerness上的Greyhound {1}},这是疯了)。

答案 2 :(得分:1)

根据容器(例如:List),使用差异来表示子类型。在大多数语言中,如果函数请求Class Animal的对象,则传递任何继承Animal的类(例如:Dog)将是有效的。但是,就容器而言,这些无需有效。 如果您的函数需要Container[A],那么可以传递给它的可能值是什么?如果B延伸A并且传递Container[B]有效,则协变(例如:List[+T])。如果,A扩展B(相反的情况)并且Container[B]的{​​{1}}通过是有效的,那么它是 Contravariant 。否则,它是不变的(这是默认值)。您可以参考我尝试解释Scala https://lakshmirajagopalan.github.io/variance-in-scala/中的差异的文章。