Skip to content

Instantly share code, notes, and snippets.

@julienrf
Last active December 14, 2015 04:49
Show Gist options
  • Save julienrf/5031236 to your computer and use it in GitHub Desktop.
Save julienrf/5031236 to your computer and use it in GitHub Desktop.
Contravariance is useful.
// Some data type definitions: the usual (and boring) zoo class hierarchy
abstract class Animal { }
abstract class Mammal extends Animal { }
class Giraffe extends Mammal { }
class Zebra extends Mammal {
num stripeCount;
Zebra(num stripeCount) {
this.stripeCount = stripeCount;
}
}
// Veterinaries treat animals
abstract class Vet<A> {
void treat(A a);
}
// This one only knows how to treat zebras
class ZebraVet extends Vet<Zebra> {
void treat(Zebra zebra) {
print("Treating a zebra with ${zebra.stripeCount} stripes");
}
}
// This one knows how to treat any kind of animal
class AnimalVet extends Vet<Animal> {
void treat(Animal animal) {
print("Treating animal $animal");
}
}
// At this point of the code, it is obvious that `Vet<Animal>` is a subtype of `Vet<Zebra>`
// because a `Vet<Animal>` knows how to treat any animal, including zebras.
// This function needs just a `Vet<Mammal>`
void treatMammal(Vet<Mammal> vet) {
vet.treat(new Giraffe()); // Note that the actual mammal we want to treat is a giraffe
}
// Ok, enough definitions. Let’s run the code.
void main() {
// Can I use a `Vet<Zebra>` to treat a mammal?
// Obviously I can not (because a mammal may not be a zebra and may not have stripes).
// However the Dart analyzer does not complain … but it fails at runtime.
treatMammal(new ZebraVet());
// Can I use a `Vet<Animal>` to treat a mammal?
// Obviously I can, because a mammal *is* an animal.
// However the Dart analyzer warns me: “'AnimalVet' is not assignable to 'Vet<Mammal>'”. This assertion is just wrong.
// Hopefully the compiler lets me compile the code although the analyzer detected a type “error” … and it just runs fine.
treatMammal(new AnimalVet());
}
@engleek
Copy link

engleek commented Feb 28, 2013

I'm not sure why Dart should complain or not here.

treatMammal takes a Vet<Mammal> on input.

You can give it a ZebraVet because it is an Vet<Zebra>, and therefore extends Mammal. It can cast from Zebra to Mammal, and you will lose some methods but it will work because the Mammal properties are guarantied.

However, if you give it a AnimalVet then it should complain, because Animal is extended by Mammal, and there is no guaranty that Animal has the same properties.

So if I have Zebra > Mammal > Animal, you should be able to give treatMammal instances of Zebra because they extend Mammal, but not instances of Animal that are extended by Mammal. Right?

@julienrf
Copy link
Author

julienrf commented Mar 1, 2013

So if I have Zebra > Mammal > Animal, you should be able to give treatMammal instances of Zebra because they extend Mammal, but not instances of Animal that are extended by Mammal. Right?

No. Just the opposite.

Vet<Zebra> can only treat zebras (its treat method takes a Zebra as parameter). So it can not treat other kinds of mammals or any other animal than a zebra.

Vet<Animal> can treat animals. So it can treat any mammal, including zebras, giraffes and so on.

treatMammal takes a Vet<Mammal>, that is something able to treat any kind of mammal. A Vet<Zebra> can only treat zebras so it should not be passed to treatMammal (and if you try to run the above code it will crash because the Vet<Zebra> expects to treat a Zebra but it is given a Giraffe).

@julienrf
Copy link
Author

julienrf commented Mar 1, 2013

By the way, the fact that it is not obvious to you that Vet<Animal> is a subtype of Vet<Zebra> comforts me in the idea that (sound) type systems are useful: without them you would probably have been tempted to pass a Vet<Zebra> to treatMammal and nobody would have been here to tell you that it’s wrong.

A sound type system saves headache.

@engleek
Copy link

engleek commented Mar 1, 2013

Hang on, why are you saying that I didn't understand the inheritance?

What did Zebra > Mammal > Animal mean to you? Doesn't this imply Vet<Zebra> > Vet<Mammal> > Vet<Animal> ?

@julienrf
Copy link
Author

julienrf commented Mar 1, 2013

No. Because of the way Vet<A> is defined, Zebra <: Animal implies that Vet<Zebra> >: Vet<Animal>. Pfouh, this is too much abstract, but if you just think about the meaning of what a Vet<Animal> is and what a Vet<Zebra> is, a five years old child can understand that a Vet<Animal> does more than a Vet<Zebra>. (The former treats any animal (including zebras) while the latter treats only zebras)

@engleek
Copy link

engleek commented Mar 1, 2013

Why can't I pass my function a Zebra? Isn't a Zebra a Mammal?

Why can I pass my function an Animal? Is my vet suddenly capable of treating all animals, and not just Mammals?

You can cast down, but you can't cast up.

I'm so lost here I asked others to tell me I wasn't crazy, but they're telling me I'm not.

@julienrf
Copy link
Author

julienrf commented Mar 1, 2013

A Zebra is a Mammal, but a Vet<Zebra> is not a Vet<Mammal> ;)

A Vet<Mammal> is able to treat any Mammal (including a Zebra) while a Vet<Zebra> is only able to treat a Zebra. So a Vet<Mammal> is a Vet<Zebra>!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment