Variances gives a very high-level overview of implementing contravariance and covariance with respect to Generics. Examples and definitions below.
Scala allows more flexible use of generics than Java. Specifically, you can decide that if
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).
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.
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[Child] is a subtype of
Foo[Parent]. If, instead, we were to declare
Foo[-T], that would mean the opposte:
Foo[Child] is a supertype of
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
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
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 has no
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
Function1 is declared as
Function1[-P,+R], meaning that
Function1[Lookup,String] is actually a subtype of
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
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
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.
Last Updated 08/22/2009 at 10:48:30 AM by davecblog comments powered by Disqus
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.