Чем отличаются upper bound и lower bound?

В Scala upper bound и lower bound — это ограничения на типовые параметры, которые позволяют контролировать допустимые типы в обобщениях. Они широко используются для ограничения области значений параметризованных типов и обеспечения типобезопасности. Синтаксически эти ограничения указываются с помощью <: (upper bound) и >: (lower bound).

Upper Bound (<:)

Определение: Upper bound (T <: U) указывает, что тип T должен быть подтипом типа U.

Пример:

class Animal
class Dog extends Animal
class Cat extends Animal
def printAnimalInfo\[T <: Animal\](animal: T): Unit = {
println(animal.getClass.getSimpleName)
}

В этом примере T должен быть подтипом Animal. Соответственно, printAnimalInfo(new Dog) и printAnimalInfo(new Cat) допустимы, а printAnimalInfo("String") вызовет ошибку компиляции.

Зачем нужен upper bound:

  • Чтобы ограничить использование метода или класса определённой иерархией типов.

  • Для доступа к членам типа U внутри обобщённого кода, так как T гарантированно имеет свойства U.

Lower Bound (>:)

Определение: Lower bound (T >: U) указывает, что тип T должен быть супертипом типа U.

Пример:

class Animal
class Dog extends Animal
class Cat extends Animal
def addAnimalToList\[T >: Dog\](list: List\[T\], dog: Dog): List\[T\] = {
dog :: list
}

В этом случае T должен быть супертипом Dog, то есть T может быть Animal, AnyRef или Any.

Зачем нужен lower bound:

  • Когда нужно безопасно добавлять значения в коллекцию, сохраняя корректную типизацию.

  • Позволяет гарантировать, что тип параметра допускает определённый подтип.

Комбинация Upper и Lower Bounds

Можно использовать и верхнюю, и нижнюю границу одновременно:

def process\[T >: Dog <: Animal\](value: T): Unit = {
// T  это тип от Dog до Animal (включительно)
}

Этот код означает, что T должен быть супертипом Dog, но при этом подтипом Animal, то есть либо Dog, либо Animal.

Пример с ковариантными контейнерами

abstract class Animal {
def speak(): Unit
}
class Dog extends Animal {
def speak(): Unit = println("Woof")
}
class Cat extends Animal {
def speak(): Unit = println("Meow")
}
def makeAnimalSpeak\[T <: Animal\](animal: T): Unit = {
animal.speak()
}

Только те типы, которые являются подтипами Animal, смогут быть переданы. Это позволяет использовать speak() без дополнительных проверок типов.

Почему это важно: PECS (Producer Extends, Consumer Super)

Этот принцип (из Java, но применим и в Scala):

  • Producer Extends (<:) — если вы чтo-тo извлекаете из обобщённого объекта, используйте upper bound.

  • Consumer Super (>:) — если вы что-то добавляете в обобщённый объект, используйте lower bound.

Пример:

def copyElements\[S, D >: S\](from: List\[S\], to: List\[D\]): List\[D\] = {
from ::: to
}

D >: S гарантирует, что to может принять элементы из from, потому что каждый S можно безопасно привести к D.

Bounds в контексте типа параметра класса

class Cage\[T <: Animal\](val animal: T)

Здесь Cage может содержать Dog, Cat, но не String.

Пример с коллекциями

def prependToList\[T >: Int\](list: List\[T\], element: Int): List\[T\] = {
element :: list
}

Вы можете добавлять Int в список любого типа, который является супертипом Int: Int, AnyVal, Any.

Пример с максимальным элементом

def max\[T &lt;: Ordered\[T\]\](a: T, b: T): T = if (a &gt; b) a else b

Здесь T ограничен типами, реализующими Ordered[T]. Это позволяет использовать оператор >.

Типичные ошибки

  1. **Неправильная граница:
    **
// Ошибка: компилятор не сможет вывести типы
def wrong\[T >: Animal\](a: T): T = a
  1. **Путаница в направлении:
    **
// Неверно, если хотите вызывать методы Dog
def handleAnimal\[T >: Dog\](animal: T): Unit = {
// animal.bark()  ошибка
}

Нужно использовать <: Dog, если нужно вызвать методы Dog.

Вывод из границ при сопоставлении типов

Если определён T >: Dog <: Animal, то возможны следующие варианты для T:

  • Dog

  • Animal

T не может быть Cat, потому что Cat <: Animal, но Cat не является > Dog.

Таким образом, upper и lower bounds в Scala дают точный контроль над допустимыми типами в обобщённом программировании и позволяют писать безопасный, выразительный и гибкий код.