I only found one good and simple explanation of generics Contravariance in Julien Richard-Foy’s blog:
http://julien.richard-foy.fr/blog/2013/02/21/be-friend-with-covariance-and-contravariance/
However it was not clear enough for me to memorize all these details, so I decided to develop the idea in a bit different key.
Let’s define some classification of animals:
Covariance
The easy example of covariance is a group of animals of the same kind (flock, heard, whatever...)
So the group of cows is also a group of ungulate, which is group of mammals, whch is a also group of animals:
Cow -> Ungulate -> Mammal -> Animal
Group[Cow] -> Group[Ungulate] -> Group[Mammals] -> Group[Animals]
This is covariance where Cow is a subclass of Animal, the Group[of Cow] is a (subtype of) Group[of Animal]
Contravariance
Imagine that there’s a law describing Veterinary licensing. The law defines the hierarchy of animal classes as above.
The imaginable law states that a Veterinarian with a license of some class is allowed to treat animals of subclasses of this class of animals, therefore:
LicensedVet[for Cows] can only treat Cows
LicensedVet[for Cattle] can only treat Sheep and Cows
LicensedVet[for Mammals] can only treat Dogs, Cats, Sheep and Cows
LicensedVet[for Animals] can treat anyone from this hierarchy
Cow -> Cattle -> Mammal -> Animal
LicensedVet[Cow] <- LicensedVet[Ungulate] <- LicensedVet[Mammal] <- LicensedVet[Animals]
This is contravariance here: Cow is a subclass of Animal, but LicensedVeterinarian[for cows] is not a (subtype of) LicensedVeterinarian[for all Animals], however the LicensedVeterinarian[for Animals] is a (kind of subtype of) LicensedVeterinarian[for Cows].
This is a practical example of contravariance “from the real life” with kind of practical application.
Ok, let’s do some coding (in Scala):
Everything seems to work as expected, what for do we need all this covariance and contravariance things?
Imagine that vet can treat a group of animals:
If we try to compile the code with James Herriot doing treatGroupOfAnimals compiler will sipt something like:
type mismatch, expected: GroupOfAnimal[Animal], actual: GroupOfAnimal[Cattle]
What? A flock of cattle is not a group of animals? It happens because parametrized types are non-variant by default, so we must define whether it Covariant or Contravariant, otherwise it is considered to be non-variant.
Group of animals is covariant to animals, as mentioned above. So, if we redefine group of animals as follows, James Herriot will be able to treat a group of animals:
Ok. What’s the use for contravariance? Let’s imagine that we have a farm:
Phew… With contravariance option we can invite James Herriot. Our farm is safe now.
The final code:
http://julien.richard-foy.fr/blog/2013/02/21/be-friend-with-covariance-and-contravariance/
However it was not clear enough for me to memorize all these details, so I decided to develop the idea in a bit different key.
Let’s define some classification of animals:
Covariance
The easy example of covariance is a group of animals of the same kind (flock, heard, whatever...)
So the group of cows is also a group of ungulate, which is group of mammals, whch is a also group of animals:
Cow -> Ungulate -> Mammal -> Animal
Group[Cow] -> Group[Ungulate] -> Group[Mammals] -> Group[Animals]
This is covariance where Cow is a subclass of Animal, the Group[of Cow] is a (subtype of) Group[of Animal]
Contravariance
Imagine that there’s a law describing Veterinary licensing. The law defines the hierarchy of animal classes as above.
The imaginable law states that a Veterinarian with a license of some class is allowed to treat animals of subclasses of this class of animals, therefore:
LicensedVet[for Cows] can only treat Cows
LicensedVet[for Cattle] can only treat Sheep and Cows
LicensedVet[for Mammals] can only treat Dogs, Cats, Sheep and Cows
LicensedVet[for Animals] can treat anyone from this hierarchy
Cow -> Cattle -> Mammal -> Animal
LicensedVet[Cow] <- LicensedVet[Ungulate] <- LicensedVet[Mammal] <- LicensedVet[Animals]
This is contravariance here: Cow is a subclass of Animal, but LicensedVeterinarian[for cows] is not a (subtype of) LicensedVeterinarian[for all Animals], however the LicensedVeterinarian[for Animals] is a (kind of subtype of) LicensedVeterinarian[for Cows].
This is a practical example of contravariance “from the real life” with kind of practical application.
Ok, let’s do some coding (in Scala):
class Animal(val name: String) { override def toString = this.getClass.getSimpleName+": "+name } class Mammal(name: String) extends Animal(name) class Dog(name: String) extends Mammal(name) class Cat(name: String) extends Mammal(name) class Cattle(name: String) extends Mammal(name) class Sheep(name: String) extends Cattle(name) class Cow(name: String) extends Cattle(name) class Bird(name: String) extends Animal(name) class Goose(name: String) extends Bird(name) class Chicken(name: String) extends Bird(name) class GroupOfAnimals[A](val list: List[A]) { def getOne = list.head def getByName(name: String) = list.find({ case p: Animal if p.name == name => true case _ => false } ) } class LicensedVeterinarian[A] { def treat(a: A) = { print("Treat animal ") print(a) } } val dorothySheep = new Sheep("Dorothy") val murkaCow = new Cow("Murka") val gavCat = new Dog("Gav") val unnamedHen = new Chicken("Hen") val flockOfSheep = new GroupOfAnimals[Sheep](List(dorothySheep)) val someCattle = new GroupOfAnimals[Cattle](List(murkaCow, dorothySheep)) val jamesHerriot = new LicensedVeterinarian[Animal] val bobbySmith = new LicensedVeterinarian[Cattle] flockOfSheep.getOne //Sheep: Dorothy someCattle.getOne //Cow: Murka
someCattle.getByName("Murka") //Some(Cow: Murka) //mr. Herriot can treat anyone jamesHerriot.treat(dorothySheep) jamesHerriot.treat(gavCat) jamesHerriot.treat(unnamedHen)
//Bobby licensed only for Cattle bobbySmith.treat(dorothySheep) //bobbySmith.treat(gavCat) //type mismatch, expected: Cattle, actual: Cat //bobbySmith.treat(unnamedHen) //expected: Cattle, actual: Chicken
Everything seems to work as expected, what for do we need all this covariance and contravariance things?
Imagine that vet can treat a group of animals:
class LicensedVeterinarian[A] { def treat(a: A) = { print("Treat animal ") print(a) } def treatGroupOfAnimals(g: GroupOfAnimals[A]) = { print("Treat animals ") print(g) } }
jamesHerriot.treatGroupOfAnimals(someCattle)
If we try to compile the code with James Herriot doing treatGroupOfAnimals compiler will sipt something like:
type mismatch, expected: GroupOfAnimal[Animal], actual: GroupOfAnimal[Cattle]
What? A flock of cattle is not a group of animals? It happens because parametrized types are non-variant by default, so we must define whether it Covariant or Contravariant, otherwise it is considered to be non-variant.
Group of animals is covariant to animals, as mentioned above. So, if we redefine group of animals as follows, James Herriot will be able to treat a group of animals:
class GroupOfAnimals[+A](val list: List[A]) { def getOne = list.head def getByName(name: String) = list.find({ case p: Animal if p.name == name => true case _ => false } ) }jamesHerriot.treatGroupOfAnimals(someCattle) //now works as expected
Ok. What’s the use for contravariance? Let’s imagine that we have a farm:
Weird! Bobby can be invited to treat Cattle, but James Herriot cannot. As we said above, licensed veterinarians are contravariant to animals, so let’s try make them contravariant:object Farm { // This one will not work without covariance of GroupOfAnimals def areAnimalsOk(group: GroupOfAnimals[Mammal]): Boolean = { // Dunno am not a vet, they're always lookin fine true } def inviteVetForCattle(veterinarian: LicensedVeterinarian[Cattle]): Unit = { println("Inviting veterinarian for cattle") } def inviteVetForDog(veterinarian: LicensedVeterinarian[Dog]): Unit = { println("Inviting veterinarian for dog") } def inviteVetForBirds(veterinarian: LicensedVeterinarian[Bird]): Unit = { println("Inviting veterinarian for birds") } } Farm.areAnimalsOk(flockOfSheep) //True: They are always OK Farm.inviteVetForCattle(bobbySmith) // ^ This one is OK, Bobby is veterinarian for Cattle Farm.inviteVetForCattle(jamesHerriot) //expected:LicensedVeterinarian[Cattle],actual:LicensedVeterinarian[Animal]
class LicensedVeterinarian[-A] { def treat(a: A) = { print("Treat animal ") print(a) } def treatGroupOfAnimals(g: GroupOfAnimals[A]) = { print("Treat animals ") print(g) } } Farm.inviteVetForCattle(jamesHerriot)
Phew… With contravariance option we can invite James Herriot. Our farm is safe now.
The final code:
class Animal(val name: String) { override def toString = this.getClass.getSimpleName+": "+name } class Mammal(name: String) extends Animal(name) class Dog(name: String) extends Mammal(name) class Cat(name: String) extends Mammal(name) class Cattle(name: String) extends Mammal(name) class Sheep(name: String) extends Cattle(name) class Cow(name: String) extends Cattle(name) class Bird(name: String) extends Animal(name) class Goose(name: String) extends Bird(name) class Chicken(name: String) extends Bird(name) class GroupOfAnimals[+A](val list: List[A]) { def getOne = list.head def getByName(name: String) = list.find({ case p: Animal if p.name == name => true case _ => false } ) } class LicensedVeterinarian[-A] { def treat(a: A) = { print("Treat animal ") print(a) } def treatGroupOfAnimals(g: GroupOfAnimals[A]) = { print("Treat animals ") print(g) } } val dorothySheep = new Sheep("Dorothy") val murkaCow = new Cow("Murka") val gavCat = new Dog("Gav") val unnamedHen = new Chicken("Hen") val flockOfSheep = new GroupOfAnimals[Sheep](List(dorothySheep)) val someCattle = new GroupOfAnimals[Cattle](List(murkaCow, dorothySheep)) val jamesHerriot = new LicensedVeterinarian[Animal] val bobbySmith = new LicensedVeterinarian[Cattle] flockOfSheep.getOne //Sheep: Dorothy someCattle.getOne //Cow: Murka someCattle.getByName("Murka") //Some(Cow: Murka) //mr. Herriot can treat anyone jamesHerriot.treat(dorothySheep) jamesHerriot.treat(gavCat) jamesHerriot.treat(unnamedHen) //Bobby licensed only for Cattle bobbySmith.treat(dorothySheep) //bobbySmith.treat(gavCat) //type mismatch, expected: Cattle, actual: Cat //bobbySmith.treat(unnamedHen) //expected: Cattle, actual: Chicken jamesHerriot.treatGroupOfAnimals(someCattle) object Farm { // This one will not work without covariance of GroupOfAnimals def areAnimalsOk(group: GroupOfAnimals[Mammal]): Boolean = { // Dunno am not a vet, they're always lookin fine true } def inviteVetForCattle(veterinarian: LicensedVeterinarian[Cattle]): Unit = { println("Inviting veterinarian for cattle") } def inviteVetForDog(veterinarian: LicensedVeterinarian[Dog]): Unit = { println("Inviting veterinarian for dog") } def inviteVetForBirds(veterinarian: LicensedVeterinarian[Bird]): Unit = { println("Inviting veterinarian for birds") } } Farm.areAnimalsOk(flockOfSheep) Farm.inviteVetForCattle(bobbySmith) Farm.inviteVetForCattle(jamesHerriot) Farm.inviteVetForDog(jamesHerriot) Farm.inviteVetForBirds(jamesHerriot) //Bobby is only licensed for cattle, not for birds or dogs: //Farm.inviteVetForDog(bobbySmith) // Nope, type mismatch //Farm.inviteVetForBirds(bobbySmith // Nope, type mismatch
No comments:
Post a Comment