[英]Contravariance vs Covariance in Scala
我剛學了Scala。 現在我對逆變和協方差感到困惑。
從這個頁面,我學到了以下內容:
協方差
也許子類型最明顯的特性是能夠在表達式中用較窄類型的值替換較寬類型的值。 例如,假設我有一些類型Real
、 Integer <: 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
參數x
是contravariant
; 它需要一個List[Dog]
類型的參數,但我們給它一個List[Beagle]
,這沒關系
[我認為第二個例子也應該證明Covariance
。 因為從第一個示例中,我了解到“將此函數應用於Integer
類型(或Real
任何其他子類型)的值”。 因此,相應地,在這里我們將此函數應用於List[Beagle]
類型(或List[Dog]
任何其他子類型)的值。 但令我驚訝的是,第二個例子證明了Cotravariance
]
我認為兩個在談論同一件事,但一個證明Covariance
,另一個證明Contravariance
。 我也從 SO看到了這個問題。 然而我仍然很困惑。 我錯過了什么還是其中一個例子是錯誤的?
最近一篇關於該主題的好文章(2016 年 8 月)是Matt Handler 的“ 逆變和協方差的作弊代碼”。
它從“ 主機和訪問者的協方差和逆變”中提出的一般概念以及來自Andre Tyukin和anoopelias的回答的圖表開始。
它的結論是:
以下是如何確定您的
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
,這是瘋狂)。
您可以將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.