I understand covariance and contravariance in scala. Covariance has many applications in the real world, but I can not think of any for contravariance applications, except the same old examples for Functions.
Can someone shed some light on real world examples of contravariance
use?
This is useful whenever you want to access some common method or property of a collection which can contain items of one or more types derived from a base class. For example, you might have a hierarchy representing different categories of stock for a supermarket.
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.
Covariance is a concept that is very straightforward to understand. We say that a type constructor F[_] is covariant if B is a subtype of type A and F[B] is a subtype of type F[A]. In Scala, we declare a covariant type constructor using the notation F[+T], adding a plus sign on the left of the type variable.
Language. Variance is the correlation of subtyping relationships of complex types and the subtyping relationships of their component types. Scala supports variance annotations of type parameters of generic classes, to allow them to be covariant, contravariant, or invariant if no annotations are used.
In my opinion, the two most simple examples after Function
are ordering and equality. However, the first is not contra-variant in Scala's standard library, and the second doesn't even exist in it. So, I'm going to use Scalaz equivalents: Order and Equal.
Next, I need some class hierarchy, preferably one which is familiar and, of course, it both concepts above must make sense for it. If Scala had a Number
superclass of all numeric types, that would have been perfect. Unfortunately, it has no such thing.
So I'm going to try to make the examples with collections. To make it simple, let's just consider Seq[Int]
and List[Int]
. It should be clear that List[Int]
is a subtype of Seq[Int]
, ie, List[Int] <: Seq[Int]
.
So, what can we do with it? First, let's write something that compares two lists:
def smaller(a: List[Int], b: List[Int])(implicit ord: Order[List[Int]]) = if (ord.order(a,b) == LT) a else b
Now I'm going to write an implicit Order
for Seq[Int]
:
implicit val seqOrder = new Order[Seq[Int]] { def order(a: Seq[Int], b: Seq[Int]) = if (a.size < b.size) LT else if (b.size < a.size) GT else EQ }
With these definitions, I can now do something like this:
scala> smaller(List(1), List(1, 2, 3)) res0: List[Int] = List(1)
Note that I'm asking for an Order[List[Int]]
, but I'm passing a Order[Seq[Int]]
. This means that Order[Seq[Int]] <: Order[List[Int]]
. Given that Seq[Int] >: List[Int]
, this is only possible because of contra-variance.
The next question is: does it make any sense?
Let's consider smaller
again. I want to compare two lists of integers. Naturally, anything that compares two lists is acceptable, but what's the logic of something that compares two Seq[Int]
being acceptable?
Note in the definition of seqOrder
how the things being compared becomes parameters to it. Obviously, a List[Int]
can be a parameter to something expecting a Seq[Int]
. From that follows that a something that compares Seq[Int]
is acceptable in place of something that compares List[Int]
: they both can be used with the same parameters.
What about the reverse? Let's say I had a method that only compared ::
(list's cons), which, together with Nil
, is a subtype of List
. I obviously could not use this, because smaller
might well receive a Nil
to compare. It follows that an Order[::[Int]]
cannot be used instead of Order[List[Int]]
.
Let's proceed to equality, and write a method for it:
def equalLists(a: List[Int], b: List[Int])(implicit eq: Equal[List[Int]]) = eq.equal(a, b)
Because Order
extends Equal
, I can use it with the same implicit above:
scala> equalLists(List(4, 5, 6), List(1, 2, 3)) // we are comparing lengths! res3: Boolean = true
The logic here is the same one. Anything that can tell whether two Seq[Int]
are the same can, obviously, also tell whether two List[Int]
are the same. From that, it follows that Equal[Seq[Int]] <: Equal[List[Int]]
, which is true because Equal
is contra-variant.
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