I remember when I started to learn Scala, I usually come up with brackets in the Scala API doc that have plus and minus characters inside. Honestly that scared me a little bit! But after I learned a bit about Scala type system, I know the purpose of theses type declarations and I appreciate their designer :)
In this series of posts I will try my best to clear out type variance annotations in Scala; a powerful tool to handle type orderings from specific to generic and vice verca.
Covariant Subtyping
We start by covariant subtyping. If you don’t know what it is, please be patient. First consider the following code in Scala:
We have a Company class that has type parameter T. This class accepts a value of type T for its constructor. We have two companies defined here: BigCompany and SmallCompany. As it is obvious from the code SmallCompany is a subtype of BigCompany. Finally we have a class Investor that accepts only companies of type BigCompany so it invests only on big ones!
Now let’s do some constructions in REPL:
scala> val littleCompany: Company[SmallCompany] = new Company[SmallCompany](new SmallCompany) littleCompany: Company[SmallCompany] = Company@1e70822 scala> val bigCompany: Company[BigCompany] = new Company[BigCompany](new BigCompany) bigCompany: Company[BigCompany] = Company@9bb740 scala> val bigInvestor:Investor = new Investor(bigCompany) bigInvestor: Investor = Investor@1004566
So far so good. After a while it is decided that we need investors that can take care of small companies. We don’t want to define a new brand of investor thus we use the existing one. So we try to construct:
scala> val smallInvestor:Investor = new Investor(littleCompany)
But we get:
<console>:8: error: type mismatch; found : Company[SmallCompany] required: Company[BigCompany] Note: SmallCompany <: BigCompany, but class Company is invariant in type T. You may wish to define T as +T instead. (SLS 4.5) val smallInvestor:Investor = new Investor(littleCompany) ^
Bang! What happened? Compile error!
Let’s see what happened exactly here. We know by definition that SmallCompany is subtype of BigCompany. But what is the relation between Company[SmallCompany] and Company[BigCompany] ? It is not defined by default and we need somehow to tell compiler about this. In order to solve this we change class Company to the following:
The only thing that is changed is the type parameter that has a + sign in front. This means that subtyping is covariant (flexible) in paramter T.
Covariant subtyping means: if S is a subtype of T then Company[S] is subtype of Company[T].
Remember that covariant orders types from more specific to more generic.
Let’s construct a small investor again with new Company definition:
scala> val smallInvestor:Investor = new Investor(littleCompany) smallInvestor: Investor = Investor@9fad9c
Great! We could solve this by Scala variance subtyping. By a single + sign that is called variance annotation you could tell Scala compiler that you want Company[SmallCompany] be a subtype of Company[BigCompany].
In the next post, we will investigate contravariant subtyping.
I’ve read this described abstractly about 5 different times, and you’re the first to put in a very clear example showing why this is important.
Thanks so much.
Lloyd Muccio
Pingback: itemprop="name">Scala Type Variances – Part two | Jayway Team Blog - Sharing Experience
Pingback: itemprop="name">Scala Type Variances – part three | Jayway Team Blog - Sharing Experience
The first it has become clear to me. Good job!
thanks, great post
Very clear and neat job dude!
Really a clear and upto the point explanation. Thank you.