Monday 19 October 2015

Practical generic types Contravariant and Covariant subtyping in Scala

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):
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:
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]
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:
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



Friday 2 October 2015

Sequence of cases in scala

 There's not much said about sequences of cases (sequence of cases) in the "Programming in Scala: second edition". Googling returns a bit different stuff. Essentially they are just more concise way to define match cases and they not only work with Option, but with any other types as well. The code below can describe usage of sequences of cases:

var capitals = Map("France"->"Paris", "Japan"->"Tokyo", "Russia"->"Moscow")

/* sequence of cases */
val withDefaultSequenceOfCases: Option[String] => String = {
  case Some(x) => x // pattern match to extract string from Some
  case None => "?"
}// withDefaultSequenceOfCases: Option[Int] => Int = <function1>

/* match representation of sequence of cases */
def withDefaultMatch(s: Option[String]): String = s match {
  case Some(x) => x
  case None => "?"
}// withDefaultMatch: Option[String] => String = <function1>

capitals get "Russia" //res0: Option[String] = Some(Moscow)
capitals get "Sumatra" //Option[String] = None
capitals("Russia") //String = Moscow
withDefaultSequenceOfCases(Some("Test")) //String = Test
withDefaultSequenceOfCases(capitals get "Japan") //String = Tokyo
withDefaultMatch(capitals get "France") //String = Paris
withDefaultSequenceOfCases(capitals get "Somewhere") //String = ?
withDefaultMatch(capitals get "Pacific") //String = ?

/* sequence of cases is not necessarily Option */
abstract class Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr

val renderUnary: Expr => String = {
  case UnOp(op, arg:Number) => op+""+arg.num
  case UnOp(op, arg) => op+""+arg
  case _ => "non unary operator"
}

renderUnary(UnOp("-",Number(1))) //String = -1.0
renderUnary(UnOp("+",Number(2))) //String = +2.0
renderUnary(Number(1)) //String = not unary operator