I'm trying to implement some function in a super class so I don't have to always repeat it in its children. Sample:
trait Animal {
def applyF(transition: Animal => Animal): Animal = transition(this) // Animal as param and return type
}
case class Cat(color: String) extends Animal {
def changeColor(color: String): Cat = this.copy(color)
def update(): Animal = {
val transition = (cat: Cat) => cat.changeColor("yellow") // Cat as param and return type
applyF(transition) // <-- Type mismatch, expected: Animal => Animal, actual: Cat => Cat
}
}
But this gives a type mismatch because Cat
is not Animal
. Why does this not work?
Cat extends Animal so it should be an animal right?
Has this something to do with co-/contravariant?
How can I fix it?
----- Update -----
Second example:
trait Animal {
def applyF[A >: this.type <: Animal](transitions: Iterable[A => Animal]): Animal =
transitions.foldLeft(this)((animal, transition) => transition(animal))
}
case class Cat(color: String) extends Animal {
def changeColor(color: String): Cat = this.copy(color)
def update(): Animal = {
val transition = (cat: Cat) => cat.changeColor("yellow") // Cat as param and return type
applyF(Iterable(transition)) // <-- Type mismatch, expected: A, actual: entity.type (with underlying type example.state.Entity)
}
}
Cat
extends Animal
but Cat => Cat
doesn't extend Animal => Animal
.
A => B
is covariant with respect to B
and contravariant with respect to A
, i.e. if A1 <: A
, B1 <: B
then A => B1 <: A => B <: A1 => B
.
What if you parametrize Animal#applyF
?
trait Animal {
def applyF[A >: this.type <: Animal](transition: A => Animal): Animal = transition(this)
}
trait Animal {
def applyF[A >: this.type <: Animal](transitions: Iterable[A => A]): Animal /*A*/ =
transitions.foldLeft[A](this)((animal, transition) => transition(animal))
}