After reading some articles such as
https://docs.scala-lang.org/tour/variances.html
https://www.freecodecamp.org/news/understand-scala-variances-building-restaurants/
I find I still do not understand Contravariance very well.
I can see (I suppose I do not really understand in fact) individual things like
Cat is a sub type of Animal so contravariance Printer[Animal] is a sub type of Printer[Cat]
But I fail understanding the reasons (or the link to understand those relationship). So for the code I attempted, it seems to me the last line where the parameter vegetableRecipe
(Recipe[Vegetable]) is still the sub type of Recipe[Food]
. So the chef can cook with that recipe.
trait Food extends Product with Serializable {
def name: String
}
case class Vegetable(name: String) extends Food
trait Meat extends Food
case class WhiteMeat(name: String) extends Meat
case class RedMeat(name: String) extends Meat
trait Recipe[+A] {
def name: String
def ingredients: List[A]
}
case class GenericRecipe(ingredients: List[Food]) extends Recipe[Food] {
def name = s"Generic recipe ${ingredients.map(_.name)}"
}
case class MeatRecipe(ingredients: List[Meat]) extends Recipe[Meat] {
def name = s"Meat recipe ${ingredients.map(_.name)}"
}
case class WhiteMeatRecipe(ingredients: List[Meat]) extends Recipe[Meat] {
def name = s"WhiteMeat recipe ${ingredients.map(_.name)}"
}
case class VegetableRecipe(ingredients: List[Vegetable]) extends Recipe[Vegetable] {
def name = s"Vegetable recipe ${ingredients.map(_.name)}"
}
trait Chef[-A] {
def cook(a: Recipe[A]): Unit
}
case object GenericChef extends Chef[Food] {
def cook(a: Recipe[Food]) = println(s"Generic chef can cook ${a.name}")
}
case object MeatChef extends Chef[Meat] {
def cook(a: Recipe[Meat]) = println(s"Generic chef can cook ${a.name}")
}
case object VegetableChef extends Chef[Vegetable] {
def cook(a: Recipe[Vegetable]) = println(s"Generic chef can cook ${a.name}")
}
val beef = RedMeat("beef")
val chicken = WhiteMeat("chicken")
val turkey = WhiteMeat("turkey")
val carrot = Vegetable("carrot")
val tomato = Vegetable("tomato")
val mixedRecipe = GenericRecipe(List(chicken, carrot, beef, tomato))
val meatRecipe = MeatRecipe(List(beef, chicken))
val vegetableRecipe = VegetableRecipe(List(tomato))
val genericChef = GenericChef
genericChef.cook(vegetableRecipe) // <-- here