簡體   English   中英

Scala 中的逆變與協方差

[英]Contravariance vs Covariance in Scala

我剛學了Scala。 現在我對逆變和協方差感到困惑。

從這個頁面,我學到了以下內容:

協方差

也許子類型最明顯的特性是能夠在表達式中用較窄類型的值替換較寬類型的值。 例如,假設我有一些類型RealInteger <: Real和一些不相關的類型Boolean 我可以定義一個函數is_positive :: Real -> Boolean它對Real值進行操作,但我也可以將此函數應用於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)

foo參數xcontravariant 它需要一個List[Dog]類型的參數,但我們給它一個List[Beagle] ,這沒關系

[我認為第二個例子也應該證明Covariance 因為從第一個示例中,我了解到“將此函數應用於Integer類型(或Real任何其他子類型)的值”。 因此,相應地,在這里我們將此函數應用於List[Beagle]類型(或List[Dog]任何其他子類型)的值。 但令我驚訝的是,第二個例子證明了Cotravariance ]

我認為兩個在談論同一件事,但一個證明Covariance ,另一個證明Contravariance 我也從 SO看到了這個問題 然而我仍然很困惑。 我錯過了什么還是其中一個例子是錯誤的?

最近一篇關於該主題的好文章(2016 年 8 月)是Matt Handler 的逆變和協方差的作弊代碼”。

它從“ 主機和訪問者的協方差和逆變”中提出的一般概念以及來自Andre Tyukinanoopelias回答的圖表開始。

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

它的結論是:

以下是如何確定您的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沒有(像.weinernessGreyhound ,這是瘋狂)。

您可以將List[Beagle]傳遞給期望List[Dog]的函數與函數的逆變無關,這仍然是因為 List 是協變的,而List[Beagle]List[Dog]

相反,假設您有一個功能:

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

此函數計算狗列表中的所有腿。 它接受一個函數,該函數接受一條狗並返回一個表示這條狗有多少條腿的整數。

此外,假設我們有一個函數:

def countLegsOfAnyAnimal(a: Animal): Int

可以數出任何動物的腿。 我們可以通過我們的countLegsOfAnyAnimal功能,我們countDogsLegs作為函數參數的函數,這是因為,如果這件事情可以指望任何動物的腿,它可以指望狗的腿,因為狗是動物,這是因為功能逆變。

如果查看Function1 (一個參數的函數)的定義,它是

trait Function1[-A, +B]

也就是說,它們的輸入是逆變的,而輸出是協變的。 所以Function1[Animal,Int] <: Function1[Dog,Int]因為Dog <: Animal

方差用於根據容器(例如: List )指示子類型 在大多數語言中,如果函數請求類 Animal 的對象,則傳遞任何繼承Animal類(例如: Dog )都是有效的。 然而,就容器而言,這些不一定是有效的。 如果您的函數需要Container[A] ,那么可以傳遞給它的可能值是什么? 如果B擴展A並且傳遞Container[B]是有效的,那么它是協變的(例如: List[+T] )。 如果 A 擴展了 B(逆情況)並且為Container[A]傳遞Container[B] Container[A]是有效的,那么它是Contravariant 否則,它是不變的(這是默認值)。 您可以參考一篇文章,其中我嘗試解釋 Scala 中的差異https://blog.lakshmirajagopalan.com/posts/variance-in-scala/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM