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());
}
@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