Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala contravariance - real life example

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?

like image 558
Dzhu Avatar asked Dec 26 '11 09:12

Dzhu


People also ask

Why is Contravariance useful?

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.

What is covariance and Contravariance in Scala?

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.

What is covariant in Scala?

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.

What is subtyping in Scala?

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.


1 Answers

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.

like image 106
Daniel C. Sobral Avatar answered Oct 12 '22 13:10

Daniel C. Sobral