-
-
Save julienrf/5031236 to your computer and use it in GitHub Desktop.
// 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()); | |
} |
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>
?
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)
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.
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>
!
By the way, the fact that it is not obvious to you that
Vet<Animal>
is a subtype ofVet<Zebra>
comforts me in the idea that (sound) type systems are useful: without them you would probably have been tempted to pass aVet<Zebra>
totreatMammal
and nobody would have been here to tell you that it’s wrong.A sound type system saves headache.