2014-12-11 415 views
6

我刚刚学过Scala。现在我对逆变和协变感到困惑。Scala中的协变性与协方差

从这个page,我学到下面的东西:

协方差

也许亚型的最明显的特征是在表达一个更小的类型的值来代替一个更广泛类型的值的能力。例如,假设我有一些类型Real,Integer <: 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([email protected]) 

参数fooxcontravariant;它预计类型List[Dog]类型的论据,但我们给它一个List[Beagle],这没关系

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

我认为两个人在说同一件事,但一个证明Covariance和其他Contravariance。我也看到了this question from SO。但是我仍然感到困惑。我错过了什么或者其中一个例子是错误的?

回答

9

,你可以通过一个List[Beagle],以期待List[Dog]是无关的功能逆变功能,它仍然是因为List是协变和List[Beagle]List[Dog]

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

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

此函数计算犬的列表中的所有腿。它接受一个函数接受一只狗并返回一个int,表示这条狗有多少条腿。

而且可以说,我们有一个功能:

def countLegsOfAnyAnimal(a: Animal): Int 

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

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

trait Function1[-A, +B] 

也就是说,他们是在他们自己的输出输入和协变逆变。所以Function1[Animal,Int] <: Function1[Dog,Int]因为关于该主题Dog <: Animal

+0

很好的解释。尽管'groomAnyAnimal'的返回类型应该是'Dog'来插入它,因为函数在它们的返回类型中是协变的并且仅在它们的参数类型中是逆变的。 – 2014-12-11 04:02:36

+0

@stew你认为第二个例子的陈述有些不对吗? – CSnerd 2014-12-11 04:09:46

+1

@CSnerd在你的第二个例子中,你想知道'x'是协变还是逆变。然而,正如炖菜所提到的,方差对于是否可以传递需要超类型的子类型没有任何关系(参见[Liskov Substitution Principle](http://en.wikipedia.org/wiki/Liskov_substitution_principle))。在第二个例子中唯一的协变是List,因此'List [Beagle]'是List [Dog]的一个子类型。 – 2014-12-11 04:17:46

6

最近有一个很好的文章(2016八月)是 “Cheat Codes for Contravariance and Covariance” 由Matt Handler

它从一般概念开始如在“Covariance and Contravariance of Hosts and Visitors”和图从Andre Tyukinanoopeliasanswer呈现。

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

及其与结论:

这里是如何确定的,如果你的type ParametricType[T]能/不能协变/逆变:

  • A型可协变时,不会调用上泛型的方法。
    如果类型需要调用传递给它的通用对象的方法,它不能是协变的。

原型的例子:

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

原型的例子:

`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所不具备的方法(例如Greyhound上的.weinerness,这是疯了)。

0

方差用于表示子类型容器术语(例如:List)。在大多数语言中,如果一个函数请求Class Animal的对象,则传递任何继承Animal(例如:Dog)的类将是有效的。但是,就容器而言,这些不必是有效的。 如果您的功能需要Container[A],可传递给它的可能值是多少?如果B延伸A并且通过Container[B]是有效的,则它是协变(例如:List[+T])。如果A扩展B(反例)并且通过Container[B] for Container[A]是有效的,则它是逆变。否则,它是不变的(这是默认值)。你可以参考一篇文章,我试图解释斯卡拉https://lakshmirajagopalan.github.io/variance-in-scala/中的差异。