I have read this and this answers before posting this question, but I am still a bit unclear in my understanding of this topic as explained below:
I understand what covariant and contravariant independently mean.
If I have classes as below:
class Car {}
class SportsCar extends Car {}
class Ferrari extends SportsCar {}
And:
object covar extends App {
// Test 1: Works as expected
def test1( arg: SportsCar => SportsCar ) = {
new SportsCar
}
def foo1(arg: Car): Ferrari = { new Ferrari }
def foo2(arg: SportsCar): Car = { new Ferrari }
def foo3(arg: Ferrari): Ferrari = { new Ferrari }
test1(foo1) // compiles
test1(foo2) // Fails due to wrong return type - violates return type is covariant
test1(foo3) // Fails due to wrong parameter type - violates param type is contravariant
// Test 2: Confused - why can I call test2 succesfully with ferrari
// as the parameter, and unsuccesfully with a car as the parameter?
def test2(arg: SportsCar): SportsCar = {
new Ferrari
}
val car = new Car()
val sportsCar = new SportsCar()
val ferrari = new Ferrari()
val f1 = test2(ferrari) // compiles - why?
val f2 = test2(car) // fails - why?
}
As mentioned above, in the Test 2, why can I call test2 succesfully with ferrari as the parameter, and unsuccesfully with a car as the parameter?
Does the statement Functions are contravariant in their argument types and co-variant in their return types only apply to functions which are passed as arguments? I guess I am not making appropriate distinction between the statement and the 2 tests that I wrote.
That's because you're mixing subtyping with co/contravariance.
In your first example you expect a
Function1[-T, +R]
. In that case, the rules of co/contravariance apply to the function type. In your second example, you follow simple subtyping rules.In
test1
, where you pass infoo1
which isCar => Ferrari
everything works becausefoo1
expects aCar
, but gets aSportsCar
, which is aCar
. Any method that requires aCar
can deal with a subtype because of the subtyping nature. But these rules don't work when we talk about subtyping alone.If we expand
test1
withfoo1
with the actual types, perhaps it'll be clearer:foo1:
Car
SportsCar
SportsCar
Ferrari
Everything lines up well.
In
test2
the rules change. If I expect aSportsCar
you pass in any car, then I can no longer depend on the input being a sports car and everything breaks, but if you pass in a ferrari which is actually a sports car, everything is fine.Again, let's line up the types:
test2:
SportsCar
Ferrari
(subtype)SportsCar
Car