0

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)
  }
}
Piu130
  • 1,288
  • 3
  • 16
  • 28

2 Answers2

2

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)) 
}
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Nice this works. Thanks for your fast answer. Do you have a solution for the second example I've just added with the Iterable[A => Animal] example? – Piu130 Apr 25 '19 at 20:15
  • 1
    @Piu130 Try `trait Animal { def applyF[A >: this.type <: Animal](transitions: Iterable[A => A]): Animal /*A*/ = transitions.foldLeft[A](this)((animal, transition) => transition(animal)) }` if this is enough. – Dmytro Mitin Apr 25 '19 at 20:42
  • This works. Could you add it to the answer? Do you think this is good code? – Piu130 Apr 26 '19 at 10:09
  • @Piu130 Added. It's ok. – Dmytro Mitin Apr 26 '19 at 10:15
1

Other option would be to use F-Bounded Polymorphism.

trait Animal[A <: Animal[A]] { self: A =>
  def applyF(transition: Iterable[A => A]): A = // I would use List instead of Iterable.
    transition.foldLeft(this)((animal, transition) => transition(animal))
}
final case class Cat(color: String) extends Animal[Cat] {
  def changeColor(color: String): Cat = this.copy(color)

  def update(): Cat =
    applyF(List(cat => cat.changeColor("yellow")))
}

However, keep in mind that it does bring its own problems.