18
votes

I understand the terms co-variance and contra-variance. But there is one small thing I am unable to understand. In the course "Functional Programming in Scala" on coursera, Martin Ordersky mentions that:

Functions are contravariant in their argument types and co-variant in their return types

So for example in Java, let Dog extends Animal. And let a function be :

void getSomething(Animal a){

and I have the function call as

Dog d = new Dog();
getSomething(d)

So basically what is happeneing is that Animal a = d. And according to wiki covariance is "Converting wider to narrow". And above we are converting from dog to Animal. SO isnt the argument type covariant rather than contravariant?

4

4 Answers

32
votes

This is how functions are defined in Scala:

trait Function1 [-T1, +R]  extends AnyRef

In English, parameter T1 is contravariant and result type R is covariant. What does it mean?

When some piece of code requires a function of Dog => Animal type, you can supply a function of Animal => Animal type, thanks to contravariance of parameter (you can use broader type).

Also you can supply function of Dog => Dog type, thanks to covariance of result type (you can use narrower type).

This actually makes sense: someone wants a function to transform dog to any animal. You can supply a function that transforms any animal (including dogs). Also your function can return only dogs, but dogs are still animals.

4
votes

Converting Dog to Animal is converting narrow to wider, so it's not covariance.

1
votes

I remember being confused by that very sentence when I was reading the Scala Book back in 2007. Martin delivers it as if he was talking about a language feature, but in that sentence he only states a fact about functions in general. Scala, specifically, models that fact simply by a regular trait. Since Scala has declaration-site variance, expressing those semantics is natural to the language.

Java Generics, on the other hand, support only use-site variance, so the closest one can get to co/contravariance of a function type in Java is to hand-code it at each use site:

public int secondOrderFunction(Function<? super Integer, ? extends Number> fn) {
     ....
}

(assuming an appropriately declared interface Function<P, R>, P standing for parameter type and R for return type). Naturally, since this code is in the hands of the client, and not being specific to functions at all, the statement about param type/return type variance is not applicable to any language feature of Java. It is only applicable in a broader sense, pertaining to the nature of functions.

Java 8 will introduce closures, which implies first-class functions, but, as per Jörg's comment below, the implementation will not include a fully-fledged function type.

0
votes

I think the original question about converting Dog to Animal as already been clarified but it might be of interest to note that there is a reason why functions are defined contravariant in its arguments and covariant in its return types. Let’s say you have two functions:

val f: Vertebrate => Mammal = ??? val g: Mammal => Primate = ???

As we are talking about functions, you would expect functions composition to be amongst your primitive operations. Indeed, you can compose f and g (g o f) and obtain as result a function:

val h: Vertebrate => Primate = f andThen g

But I can replace g with a subtype:

val gChild: Animal => Primate

Without breaking the composability. And gChild is a subtype of g precisely because we defined Function contravariant in its argument. As a conclusion, you can see that a function must be defined in such a way if you want to capture and preserve the idea of functions composability. You can find more details and few graphics that should help in digesting this subject here