Нижнее ограничение типа

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

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

trait Node[+B]:
  def prepend(elem: B): Node[B]

case class ListNode[+B](h: B, t: Node[B]) extends Node[B]:
  def prepend(elem: B): ListNode[B] = ListNode(elem, this)
  def head: B = h
  def tail: Node[B] = t

case class Nil[+B]() extends Node[B]:
  def prepend(elem: B): ListNode[B] = ListNode(elem, this)

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

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

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

trait Node[+B]:
  def prepend[U >: B](elem: U): Node[U]
case class ListNode[+B](h: B, t: Node[B]) extends Node[B]:
  def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
  def head: B = h
  def tail: Node[B] = t
case class Nil[+B]() extends Node[B]:
  def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)

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

trait Bird
case class AfricanSwallow() extends Bird
case class EuropeanSwallow() extends Bird
val africanSwallowList = ListNode[AfricanSwallow](AfricanSwallow(), Nil())
// africanSwallowList: ListNode[AfricanSwallow] = ListNode(
//   h = AfricanSwallow(),
//   t = Nil()
// )
val birdList: Node[Bird] = africanSwallowList
// birdList: Node[Bird] = ListNode(h = AfricanSwallow(), t = Nil())
birdList.prepend(EuropeanSwallow())
// res0: Node[Bird] = ListNode(
//   h = EuropeanSwallow(),
//   t = ListNode(h = AfricanSwallow(), t = Nil())
// )

Переменной с типом Node[Bird] можно присвоить значение africanSwallowList, а затем добавить и EuropeanSwallow.


Ссылки: