Scala Type Variances – Part one

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.

This Post Has 7 Comments

  1. Lloyd Muccio

    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

  2. migosm

    The first it has become clear to me. Good job!

  3. sheff_master

    thanks, great post

  4. politrons

    Very clear and neat job dude!

  5. Heena

    Really a clear and upto the point explanation. Thank you.

Leave a Reply