18

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?

Jacek Laskowski
  • 72,696
  • 27
  • 242
  • 420
Jatin
  • 31,116
  • 15
  • 98
  • 163

4 Answers4

32

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.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • 7
    Another thing is, that contravariant types can only appear in places, where you write something (i.e. parameter types) and covariant types can only appear in places, where you read something (i.e. return types) – drexin Nov 10 '12 at 12:37
4

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

tibtof
  • 7,857
  • 1
  • 32
  • 49
  • 7
    Adding "something extra" doesn't widen but narrows the definition, since there are less objects conforming to the most specific subclass (Dog) than to the more general (Animal). The Animal definition is wider, in the sense that accepts a greater number of possible instances. – pagoda_5b Nov 10 '12 at 12:42
1

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.

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • So is the statement: "Functions are contravariant in their argument types and co-variant in their return types" invalid in Java environment? – Jatin Nov 10 '12 at 12:23
  • Currently, yes, but not in the sense that it states a falsity about Java functions, but that it states something about a non-existent entity in Java. – Marko Topolnik Nov 10 '12 at 12:25
  • 1
    Actually, Scala doesn't have function types, either. Functions in Scala are just regular objects with a single method. In fact, functions in Scala are faked in *exactly* the same way as they are in Java. Co- and contravariance of function arguments and return values applies as much to a Java `interface Function1` as it does to a Scala `trait Function1[T, R]`. There *is* talk about adding function types to Java, but not before Java 9. – Jörg W Mittag Nov 11 '12 at 02:10
  • Just wondering, didn't the BGGA proposal sport a complete function type hierarchy? – Marko Topolnik Nov 11 '12 at 07:27
0

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

Lorenzo
  • 91
  • 1
  • 3