Tour of Scala

Нижнее Ограничение Типа

Language

В то время как верхнее ограничение типа ограничивает тип до подтипа стороннего типа, нижнее ограничение типа объявляют тип супертипом стороннего типа. Термин B >: A выражает, то что параметр типа B или абстрактный тип B относится к супертипу типа A. В большинстве случаев A будет задавать тип класса, а B задавать тип метода.

Вот пример, где это полезно:

trait List[+A] {
  def prepend(elem: A): NonEmptyList[A] = NonEmptyList(elem, this)
}

case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A]

object Nil extends List[Nothing]
trait List[+A]:
  def prepend(elem: A): NonEmptyList[A] = NonEmptyList(elem, this)

case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A]

object Nil extends List[Nothing]

В данной программе реализован связанный список. Nil представляет пустой список. Класс NonEmptyList - это узел, который содержит элемент типа A (head) и ссылку на остальную часть списка (tail). trait List и его подтипы ковариантны, потому что у нас указанно +A.

Однако эта программа не компилируется, потому что параметр elem в prepend имеет тип A, который мы объявили ковариантным. Так это не работает, потому что функции контрвариантны в типах своих параметров и ковариантны в типах своих результатов.

Чтобы исправить это, необходимо перевернуть вариантность типа параметра elem в prepend. Для этого мы вводим новый тип для параметра B, у которого тип A указан в качестве нижней границы типа.

trait List[+A] {
  def prepend[B >: A](elem: B): NonEmptyList[B] = NonEmptyList(elem, this)
}

case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A]

object Nil extends List[Nothing]
trait List[+A]:
  def prepend[B >: A](elem: B): NonEmptyList[B] = NonEmptyList(elem, this)

case class NonEmptyList[+A](head: A, tail: List[A]) extends List[A]

object Nil extends List[Nothing]

Теперь мы можем сделать следующее:

trait Bird
case class AfricanSwallow() extends Bird
case class EuropeanSwallow() extends Bird

val africanSwallows: List[AfricanSwallow] = Nil.prepend(AfricanSwallow())
val swallowsFromAntarctica: List[Bird] = Nil
val someBird: Bird = EuropeanSwallow()

// присвоить птицам (birds) африканских ласточек (swallows)
val birds: List[Bird] = africanSwallows

// добавляем новую птицу к африканским ласточкам, `B` - это `Bird`
val someBirds = africanSwallows.prepend(someBird)

// добавляем европейскую ласточку к птицам
val moreBirds = birds.prepend(EuropeanSwallow())

// соединяем вместе различных ласточек, `B` - это `Bird`, потому что это общий супертип для обоих типов ласточек
val allBirds = africanSwallows.prepend(EuropeanSwallow())

// но тут ошибка! добавление списка птиц слишком расширяет тип аргументов. -Xlint предупредит!
val error = moreBirds.prepend(swallowsFromAntarctica)    // List[Object]

Параметр ковариантного типа позволяет birds получать значение africanSwallows.

Тип, связанный с параметром типа prepend, позволяет добавлять различные разновидности ласточек и получать более абстрактный тип: вместо List[AfricanSwallow], мы получаем List[Bird].

Используйте -Xlint, чтобы предупредить, если аргумент предполагаемого типа слишком абстрактен.

Contributors to this page: