2014-12-11 421 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/中的差異。