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.