Scala Type Variances – Part two

In the previous post, I went through what covariant subtyping is. In this post we will study contravariant subtyping with a small example.

Contravariant Subtyping

Do you remember the definition of covariant? Contravariant is the other way around. I will clarify this by the following example (example is adopted from [1]). Consider we have a trait:

This trait has a write method that is responsible for accepting an argument and writing it in the current network channel. Assume that we have two NetworkChannel of type AnyRef and String:

We just don’t care about the write implementation now. There is another class that is responsible for controlling output channels:

The control method accepts an argument of type NetworkChannel[String]. Do you think we can pass NetworkChannel[AnyRef] to this method? Yes we can! NetworkChannel is declared to be contravariant in its type T.

Contrvariant subtyping means: If S is a subtype of T then NetworkChannel[T] is a subtype of NetworkChannel[S]

This seems to be a little bit strange in our example. We know that String is a subtype of AnyRef in Scala. How come NetworkChannel[AnyRef] can be a subtype of NetworkChannel[String] ?

Consider what you can do with each channel. NetworkChannel[String] accepts arguments of type String and write them in network channel. NetworkChannel[AnyRef] accepts arguments of type AnyRef and write them also in network channel. We can substitute NetworkChannel[String] with NetworkChannel[AnyRef] since whatever can be done by NetworkChannel[String] can be done also by NetworkChannel[AnyRef] (remember that NetworkChannel[AnyRef] can also write strings) but not vice versa. We can not use NetworkChannel[String] instead of NetworkChannel[AnyRef] since it only accepts strings.

Remember that contravariant orders types from more genetic to more specific.

How should I know if two types are covariants or contravariants? Read the following section.

Liskov Substitution Principle (LSP)

LSP is a principle in object-oriented programming. It states:

It is safe to assume that S is a subtype of T if you can substitute a value of type S wherever a value of type T is required without violating the desirable properties of the program [2].

So let’s say we have S as a subtype of T. If Class of type S is a subtype of Class of type T then there is a covariant subtyping between them. Otherwise if Class of type T is a subtype of Class of type S then there is contravariant subtyping between them. if there is no subtyping relation between Class of type S and T, then there is invariant subtyping relation.

I hope you have understood Scala Types variances. In the next post I will discuss Lower bounds and upper bounds.


[1]: Martin Odersky, Lex Spoon, Bill Venners, “Programming in Scala”, 2nd Edition
[2]: Barbara Liskov, Jeannette Wing, “A behavioral notion of subtyping”, ACM Transactions on Programming Languages and Systems (TOPLAS), Volume 16, Issue 6 (November 1994), pp. 1811 – 1841