I'd say "yes". But generally you don't have to (except in edge cases), library authors do. You benefit from stronger type guarantees.
It's a bit easier to think about it as ">=T" vs "<=T" though.
In one direction, you allow T and any subclass. This is probably the most common - all T subclasses should act like T, e.g. have method x() on them. You can assign a T subclass to a T variable or return it from a T-returning method. When you add something to a List<T>, it could be a T subclass, because it's "at least a T". (covariant)
In the other direction, you allow T and any superclass. My main way to think of this is for a callback, e.g. for declaring a `map` operation. If you're mapping over List<T>, you can't declare your callback as accepting a subclass of T because the list only contains "at least T". But you can accept a superclass (e.g. Object), because any T (or subclass) has that superclass. (contravariant)
---
If you don't have support for contravariance, you either give up flexibility (you can't make a reusable Object mapper) or safety (you can't guarantee the supplied callback is safe to call).
If there's no type hierarchy whatsoever, then you don't get the benefits of polymorphism. Which is the case in some languages - for those, you typically get looser call semantics (no checks, or duck typing or something) or forcing you to use e.g. match statements everywhere to do the same thing in N branches when you have N types in a list.
With functions and a type hierarchy of some kind (or implicit conversions, or whatever) you have the same kind of issues. When you declare the type of your map function (explicitly or implicitly) you're still bounded by the type you're mapping over. If you make a "(float x, returns float) x + 1e10" function, you can't use it to map over a list of doubles, because they can't be safely reduced in precision. The reverse works though, because floats can be promoted to doubles safely. You essentially have "double" as a subclass of "float". Whether it's OO or not has nothing to do with type hierarchies, OO just embraces them with reckless abandon.
Good type inference systems can hide a lot of this from you, allowing you to drop types most of the time and let the compiler specialize it / make sure it's safe to do this particular thing. But they can fail. When they do, how do you ensure safety?
Learning the term before the concept is putting the cart before the horse. But once you find yourself dealing with the fact that e.g. one can use a Source[Float] as a Source[Number] and not vice versa, whereas one can use a Sink[Number] as a Sink[Float] but not vice versa; one can use a Function[Number, Float] as a Function[Float, Float] or a Function[Number, Number] and either of those as a Function[Float, Number], but not in reverse, then it's useful to have words for these relationships so that we can talk about them.
I'd say "yes" only because this concept will exist whether or not you have a name for it. And it's a concept you will bump into in any language with any sort of generics (even Go [1])
I wrote Scala for the last 2 years and was the resident type system "expert", so I had to explain variance to people pretty often. Luckily, it's a pretty quick, mathy definition:
Some notation first:
A <: B means "A is a subtype of B"
A >: B means "A is a supertype of B"
F[_] refers to a unary type constructor.
Now for the definitions:
If F[_] is "covariant", it means that if A <: B, then F[A] <: F[B]
If F[_] is "contravariant", it means that if A >: B, then F[B] <: F[A]
Examples of covariant type constructors are List and Functions in their output. An example of a contravariant type constructor is Functions in their input type.
^ This is all that needs to be said! I can write it on a whiteboard in ~5 minutes. I'd say that's a reasonable thing to be expected to learn.
i think the way you defined it, covariant and contravariant are identical; in your contravariant definition, swap the names of "A" and "B" and you get:
if B >: A, then F[A] <: F[B]
and B >: A means the same as A <: B, so this means:
if A <: B, then F[A] <: F[B]
which is the same as your covariant definition.
Perhaps you meant:
If F[_] is "contravariant", it means that if A <: B, then F[B] <: F[A]
?
It's only meaningful if you are using a language that has static types, subtyping, and generics. That covers Java, C#, and Dart, but omits Ruby (no static types), SML (no subtyping), and Go (no generics), for example.
If you are a language where it matters, it comes in handy, even though you usually aren't aware of it. You probably already have a correct intuition of it without realizing it.
Say you have a class like:
class Enclosure<T> {
void cage(T item) { ... }
}
And assume some sort of class hierarchy like "Mammal is a subclass of Animal". If you have a method like:
The method expects an Enclosure<Mammal> and we're giving it an Enclosure<Animal>. Is that allowed? You probably intuitively see that it should be — an Enclosure<Animal> can hold any kind of animal and mammals are all animals. Your intuition is right.
"Contravariance" is the term to describe precisely what that intuition represents.
It's a bit easier to think about it as ">=T" vs "<=T" though.
In one direction, you allow T and any subclass. This is probably the most common - all T subclasses should act like T, e.g. have method x() on them. You can assign a T subclass to a T variable or return it from a T-returning method. When you add something to a List<T>, it could be a T subclass, because it's "at least a T". (covariant)
In the other direction, you allow T and any superclass. My main way to think of this is for a callback, e.g. for declaring a `map` operation. If you're mapping over List<T>, you can't declare your callback as accepting a subclass of T because the list only contains "at least T". But you can accept a superclass (e.g. Object), because any T (or subclass) has that superclass. (contravariant)
---
If you don't have support for contravariance, you either give up flexibility (you can't make a reusable Object mapper) or safety (you can't guarantee the supplied callback is safe to call).