Используйте суперметод с параметром подтипа

Я пытаюсь реализовать некоторую функцию в суперклассе, поэтому мне не нужно всегда повторять ее в своих дочерних элементах. Образец:

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
  }
}

Но это дает несоответствие типов, потому что Cat не Animal. Почему это не работает? Cat расширяет Animal, так что это должно быть животное, верно?

Это как-то связано с ко-/контравариантом?

Как я могу это исправить?

----- Обновлять -----

Второй пример:

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)
  }
}

person Piu130    schedule 25.04.2019    source источник


Ответы (2)


Cat расширяет Animal, но Cat => Cat не расширяет Animal => Animal.

A => B ковариантно по отношению к B и контравариантно по отношению к A, т. е. если A1 <: A, B1 <: B, то A => B1 <: A => B <: A1 => B.

Что, если вы параметризуете 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)) 
}
person Dmytro Mitin    schedule 25.04.2019
comment
Хорошо, что это работает. Спасибо за быстрый ответ. У вас есть решение для второго примера, который я только что добавил с примером Iterable[A =› Animal]? - person Piu130; 25.04.2019
comment
@ Piu130 Попробуйте trait Animal { def applyF[A >: this.type <: Animal](transitions: Iterable[A => A]): Animal /*A*/ = transitions.foldLeft[A](this)((animal, transition) => transition(animal)) }, если этого достаточно. - person Dmytro Mitin; 25.04.2019
comment
Это работает. Не могли бы вы добавить это к ответу? Как вы думаете, это хороший код? - person Piu130; 26.04.2019
comment
@Piu130 Добавлено. Ничего страшного. - person Dmytro Mitin; 26.04.2019

Другой вариант — использовать F-ограниченный полиморфизм< /а>.

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")))
}

Однако имейте в виду, что это приносит свои проблемы .

person Luis Miguel Mejía Suárez    schedule 25.04.2019
comment
Хорошие ссылки. Спасибо. Похоже, нет красивого способа решить проблему. - person Piu130; 25.04.2019
comment
@ Piu130 Ваши переходы всегда будут возвращать одно и то же Animal? или они могут изменить тип? - person Luis Miguel Mejía Suárez; 25.04.2019
comment
Он всегда возвращает одно и то же животное. - person Piu130; 25.04.2019