TypeVariance
The Gist
Variances gives a very high-level overview of implementing contravariance and covariance with respect to Generics. Examples and definitions below.
My Interpretation
Scala allows more flexible use of generics than Java. Specifically, you can decide that if Child
extends Parent
, then Foo[Child]
is a subtype of Foo[Parent]
(called covariance). You can also decide that Foo[Child]
is, instead, a supertype of Foo[Parent]
(called contravariance).
Covariance
As mentioned in ScalaGenerics , if we have a generic type ConfigurationOption[T]
, and two classes, Child
which extends Parent
, the class ConfigurationOption[Child]
is not a subclass of ConfigurationOption[Parent]
. Scala provides a way to make this happen, however:
In the definition of class Configuration
, we use the +T
to indicate that that T
is a covariant type. This means that, in our example, Configuration[AdvancedStringValue]
is a proper subclass of Configuration[StringValue]
. If the +T
were changed to a simple T
, this code would not compile.
Contravariance
Given that we have covariance, Contravariance exists to solve some problems that can arise when designing certain classes. Scala’s function aspects rely heavily on this.
Suppose we have a class Foo[T]
. If we change it to Foo[+T]
, now Foo[Child]
is a subtype of Foo[Parent]
. If, instead, we were to declare Foo
as Foo[-T]
, that would mean the opposte: Foo[Child]
is a supertype of Foo[Parent]
.
Why would we need or want to do this?
An example should illuminate why. It’s a bit long, so bear with me (this is a more complicated concept than some of the others being discussed).
Suppose we have a set of classes for dealing with lookup data, such as a list of U.S. States (defined by a code, a description, and an ‘area of the country’) and a list of Countries (defined by a code, a description, and an official postal abbreviation).
Since both expose a code and a description, we pull these up into a superclass called Lookup
. Further, we also have a class that creates HTML <SELECT>
controls based upon a list of Lookup
objects and it exposes a render
method that allows us to pass in a function to control the human-readable value for any given Lookup
instance.
This code doesn’t compile, even though you might thing it should.
You might think that since Country
is a subclass of Lookup
, then the function countryValue
should be a subtype of the function lookupValue
and that you should, therefore, be able to pass it in to render
. Especially since selector
was created around a list of Country
objects!
So, why can’t you?
Consider a slight change to our main application code to the following:
The code still won’t compile, but it makes more sense now why it shouldn’t: you wouldn’t want to use countrySelector
on a USState
, because USState
has no postCode
attribute.
So, what’s going on here? Let’s look at the definition of the function type. Our function
valueMaker:(Lookup) => String
could be written as
valueMaker:Function1[Lookup,String]
Function1
is declared as Function1[-P,+R]
, meaning that Function1[Lookup,String]
is actually a subtype of Function1[Country,String]
.
Now, consider this code:
This code compiles.
Here, we’ve created a special CountrySelect
that operates just on Country
objects. Of course, countryValue
works, but notice how lookupValue
also can be passed. Since lookupValue
is a subtype of countryValue
, we can safely pass it in.
This is because the code using the function valueMaker
is definitely going to send it a Country
object. Since lookupValue
accepts any Lookup
, and since Country
is a subtype of Lookup
, we know for a fact that lookupValue
can safely operate on a Country
object. Therefore, it it safe to pass in to render
.
This took me quite a while to totally get. But, it’s all in aid of maintaining static types while providing flexible syntax and features…exactly what Scala is all about. The cool thing about that, as compared to a similar implementation in a dynamic language (like Ruby) is that the compiler can catch the screwup we made by changing line 21 to use USState
instead of Country
.
My Thoughts on this Feature
This took me a while to grok, and I hope the example above helps demonstrate why its needed and what it means. This is where static typing starts to get really confusing, although things tend to work out right. The Ruby way of dealing with this is, well, to not deal with it; you are on your own to pass the right thing in. If you don’t, hopefully your unit tests cover it.
Scala lets you document your intent and have the compiler complain when the code violates it. It’s tricky, however, to read scaladoc and/or compiler output and know what’s wrong.