08 September 2007

More surprises in Scala: covariance and contravariance

I've been reading more about Scala discovering more and more nice features the modern language with static typing can bring. Some of those features look to me just as an syntactic improvement over what we already can do with another statically typed languages like C++. For example, pattern matching example from my last blog is definitely what you can do with several AST classes (they need to have type() or rtti() method) and your own implementation of visitor pattern in C++.

But there're some language features in Scala I didn't expect and didn't think about before. The perfect example IMHO is covariant and contravariant subtyping I discovered for myself just a couple of days before.

Think about this example, if we have class B that inherits class A then we can use objects of type B whenever we need objects of type A. So far so good. But what if we have template class X with A and B as parameter types? C++ tells us X<A> and X<B> are unrelated:

class A {};
class B: public A {};
template class X {};

int main()
{
B *t1 = new B;
A *t2 = t1; //ok because B inherits A

X<B> *x1 = new X<B>;
//error!!! because C++ has non-variant subtyping
// so X<B> and X<A> are different types
X<A> *x2 = x1;
}

Contrary to that, in Scala you can declare that X[B] inherits X[A] and have covariant subtyping or even declare that X[B] inherits X[A] and have contravariant subtyping:

class A {}
class B {}
class X[+Type] {} // +Type means "covariant"

val t1 = new B
val t2 = t1 // ok, B inherits A

val x1 = new X[B]
val x2 = x1 // ok too because X[B] inherits X[A]

return


Not that some elements of covariance and contravariance of types didn't exist before in familiar languages like C++ and Java. Contrary to that they did, but most likely I woulnd't have learned about them if I didn't start learning Scala which directly exposes those things as language features. Cool language Scala is... I must say, very cool ;)

5 comments:

Anonymous said...

So, what happens if you do this:

vector<B*> *b = new vector<B*>();
vector<A*> *a = b;
a->push_back(new A);
b[0]->call_some_method_only_in_B()

Kaboom!

Blogger said...

"Cool language Scala is... I must say, very cool ;)"

Master Yoda style? Nice.

Anonymous said...

mixb, that's a good example. Not all classes are covariant in their type parameter.

The Scala type checker will not let you mark a class covariant unless it is sure to be safe. If you attempted to encode that vector class in Scala, you would either have to leave out push_back, or you'd have to omit saying that vector is covariant with its type parameter.

Both choices are reasonable, and in fact the Scala collections library offers different collections making different choices here. Class List is covariant, but does not have any methods like push_back. Class ArrayBuffer is invariant, but does include methods like push_back.



-Lex Spoon

(I saw this post via Google blog search, and just thought I'd put in two cents.)

Alexander Dymo said...

> Master Yoda style? Nice.
Hehe, I'm just an apprentice yet ;)

> So, what happens if you do this:
Indeed, as Lex pointed out that problem doesn't exist in Scala because typechecker is smart and restrictive... Now I'd like to know how it is done, but that requires reading something else than "Scala by Example" ;)

Btw, any suggestions on type theory book for mere mortals?

Anonymous said...

It seems a bit strange to me that you have to define in the template class itself if it is co- or contra-variant. In Java, you can handle any generic class co- and contravariant, with "wildcards", defined on the type of your variables.

class A {}
class B extends A {}
class X<T> {}

X<A> xa = new X<A>();
X<B> xb = new X<B>();
X<? extends A> myCovariant = xb;
X<? super B> myContraVariant = xa;

For example, it makes sense to handle a List both co- and contravariant.
From a List<? extends A> all elements you retrieve are subtypes of A (but you cannot add elements)
To a List<? super B> you can add any subtype of B (but you know nothing of the type of elements you retrieve)