CSnerd CSnerd - 3 months ago 15
Scala Question

Contravariance vs Covariance in Scala

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?

Answer

That you can pass a List[Beagle] to a function expecting a List[Dog] is nothing to do with contravariance of functions, it is still because List is covariant and that List[Beagle] is a List[Dog].

Instead lets say you had a function:

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

This function counts all the legs in a list of dogs. It takes a function that accepts a dog and returns an int representing how many legs this dog has.

Furthermore lets say we have a function:

def countLegsOfAnyAnimal(a: Animal): Int

that can count the legs of any animal. We can pass our countLegsOfAnyAnimal function to our countDogsLegs function as the function argument, this is because if this thing can count the legs of any animal, it can count legs of dogs, because dogs are animals, this is because functions are contravariant.

If you look at the definition of Function1 (functions of one parameter), it is

trait Function1[-A, +B]

That is that they are contravariant on their input and covariant on their output. So Function1[Animal,Int] <: Function1[Dog,Int] since Dog <: Animal