In the previous post, I went through what covariant subtyping is. In this post we will study contravariant subtyping with a small example.
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 ). 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 .
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.
References: Martin Odersky, Lex Spoon, Bill Venners, “Programming in Scala”, 2nd Edition
: 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