In Scala for the Impatient It is said that
functions are contra-variant in their arguments and covariant in their result type
This is straightforward and easy to understand ,however in the same topic it says
However inside a function parameter ,the variance flips- its parameters are covariant
and it takes the example of foldLeft method of Iterator as :
def foldLeft[B](z : B)(op : (B, A) => B) : B
I am not getting it clearly what it says.
I tried some of blogs as
http://www.artima.com/pins1ed/type-parameterization.html
http://blog.kamkor.me/Covariance-And-Contravariance-In-Scala/
http://blogs.atlassian.com/2013/01/covariance-and-contravariance-in-scala/
But didn't get clear understanding.
Covariance allows assigning an instance to a variable whose type is one of the instance's generic type; i.e. supertype. Contravariance allows assigning an instance to a variable whose type is one of the instance's derived type; i.e. subtype.
It declares the class to be covariant in its generic parameter. For your example, it means that Option[T] is a subtype of Option[S] if T is a subtype of S .
Scala supports variance annotations of type parameters of generic classes, to allow them to be covariant, contravariant, or invariant if no annotations are used. The use of variance in the type system allows us to make intuitive connections between complex types.
Invariant: In Scala, generic types are by default invariant.
It comes down to what it means for one function to be a subtype of another. It sounds like you are comfortable with A->B is a subtype of C->D if C is subtype of A (contravariant in the input type) and B is a subtype of D (covariant in the return type).
Now consider functions that take other functions as arguments. For example, consider (A->B)->B. We just apply the same reasoning twice. The argument is a function of type A->B and the return type is B. What needs to be true to supply a function of type C->B as the input type? Since functions are contravariant in the input type C->B must be a subtype of A->B. But as we discussed in the first paragraph, that means that A must be a subtype of C. So after two applications of the reasoning in the first paragraph we find that (A->B)->B is covariant in the A position.
You can reason similarly with more complicated functions. In fact, you should convince yourself that a position is covariant if it is to the left of an even number of arrows applying to it.
A function is always contravariant in its argument type and covariant in its return type e.g.
trait Function1[-T1, +R] extends AnyRef
trait Function2[-T1, -T2, +R] extends AnyRef
Here, T1
,T2
, ..., T
n (where n <= 22
) are arguments and R
is the return type.
In higher order functions (functions that take function as argument), an argument can have the type parameter that is passed into the function
e.g.
foldLeft
in trait Iterable
Iterable
is declared as
trait Iterable[+A] extends AnyRef
and foldLeft
is decalred as
def foldLeft[B](z : B)(op : (B, A) => B) : B
Since A
is declared covariant, it can be used as the return type. But here it is instead an argument type due to
trait Function2[-T1, -T2, +R] extends AnyRef
because op : (B, A) => B
is the literal type of Function2
.
The key to this is trait Function2
is contravariant in its argument type.
Hence covariance type is appearing in method argument due to
trait Function2 is contravariant in its argument type
This is called variance flip:
That's why invariant may appear at any position (covariance/contravariance)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With