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
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
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.
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.
Converting Dog
to Animal
is converting narrow to wider, so it's not covariance.