I just learned Scala. Now I am confused about Contravariance and Covariance.
From this page, I learned something below:
Covariance
Perhaps the most obvious feature of subtyping is the ability to replace a value of a wider type with a value of a narrower type in an expression. For example, suppose I have some types Real
, Integer <: Real
, and some unrelated type Boolean
. I can define a function is_positive :: Real -> Boolean
which operates on Real
values, but I can also apply this function to values of type Integer
(or any other subtype of Real
). This replacement of wider (ancestor) types with narrower (descendant) types is called covariance
. The concept of covariance
allows us to write generic code and is invaluable when reasoning about inheritance in object-oriented programming languages and polymorphism in functional languages.
However, I also saw something from somewhere else:
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)
Parameter x
of foo
is contravariant
; it expects an argument of type List[Dog]
, but we give it a List[Beagle]
, and that's okay
[What I think is the second example should also prove Covariance
. Because from the first example, I learned that "apply this function to values of type Integer
(or any other subtype of Real
)". So correspondingly, here we apply this function to values of type List[Beagle]
(or any other subtype of List[Dog]
). But to my surprise, the second example proves Cotravariance
]
I think two are talking the same thing, but one proves Covariance
and the other Contravariance
. I also saw this question from SO. However I am still confused. Did I miss something or one of the examples is wrong?
Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.
The Scala standard library has a generic immutable sealed abstract class List[+A] class, where the type parameter A is covariant. This means that a List[Cat] is a List[Animal] .
Covariance can be translated as "different in the same direction," or with-different, whereas contravariance means "different in the opposite direction," or against-different. Covariant and contravariant types are not the same, but there is a correlation between them. The names imply the direction of the correlation.
A Good recent article (August 2016) on that topic is "Cheat Codes for Contravariance and Covariance" by Matt Handler.
It starts from the general concept as presented in "Covariance and Contravariance of Hosts and Visitors" and diagram from Andre Tyukin and anoopelias's answer.
And its concludes with:
Here is how to determine if your
type ParametricType[T]
can/cannot be covariant/contravariant:
- A type can be covariant when it does not call methods on the type that it is generic over.
If the type needs to call methods on generic objects that are passed into it, it cannot be covariant.Archetypal examples:
Seq[+A], Option[+A], Future[+T]
- A type can be contravariant when it does call methods on the type that it is generic over.
If the type needs to return values of the type it is generic over, it cannot be contravariant.Archetypal examples:
`Function1[-T1, +R]`, `CanBuildFrom[-From, -Elem, +To]`, `OutputChannel[-Msg]`
Regarding contravariance,
Functions are the best example of contravariance
(note that they’re only contravariant on their arguments, and they’re actually covariant on their result).
For example:
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
Should we be able to pass
weinerosity
as an argument toisDogCuteEnough
? The answer is no, because the functionisDogCuteEnough
only guarantees that it can pass, at most specific, aDog
to the functionf
.
When the functionf
expects something more specific than whatisDogCuteEnough
can provide, it could attempt to call a method that someDogs
don’t have (like.weinerness
on aGreyhound
, which is insane).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With