Ответ 2
X <: Y
означает, что параметр типа X
должен быть подтипом типа Y
. X >: Y
означает противоположность, X
должен быть супер-типом Y
(в обоих случаях X = Y
нормально). Эта нотация может быть противоположной интуиции, можно подумать, что собака больше, чем животное (точнее в терминах программирования, больше услуг), но по той причине, что это более точно, есть меньше собак, чем есть животные, тип Animal
содержит больше значений, чем тип Dog
, он содержит всех собак и всех страусов. Итак Animal
> : Dog
.
По той причине, что push
имеет эту подпись, я не уверен, что смогу объяснить ее лучше, чем страница, откуда приходит пример, но позвольте мне попробовать.
Он начинается с отклонения. +
в class Stack[+A]
означает, что Stack
есть covariant in A
. если X
является подтипом Y
, Stack[X]
будет подтипом Stack[Y]
. Стопка собак - также стопка животных. Для математически наклонного, если вы видите Stack как функцию от типа к типу (X - это тип, если вы передаете его в Stack, вы получаете Stack [X], что является другим типом), будучи ковариантным, означает, что он увеличивается function (with <:, отношение подтипирования - это порядки по типам).
Это кажется правильным, но это не такой простой вопрос. Это было бы не так, с рутиной push, которая его модифицирует, добавляя новый элемент, который
def push(a: A): Unit
(пример отличается, push возвращает новый стек, оставляя this
неизменным). Конечно, Stack [Dog] должен только принимать собак, чтобы их вставляли. В противном случае это уже не будет стопкой собак. Но если мы признаем, что его рассматривают как стопку животных, мы могли бы сделать
val dogs : Stack[Dog] = new Stack[Dog]
val animals : Stack[Animal] = dogs // if we say stack is covariant
animals.push(ostrich) // allowed, we can push anything in a stack of any.
val topDog: Dog = dogs.top // ostrich!
Ясно, что обработка этого стека как ковариантная является необоснованной. Когда стек рассматривается как Stack[Animal]
, допускается операция, которая не была бы на Stack[Dog]
. То, что было сделано здесь с помощью push, может быть выполнено с помощью любой процедуры, которая воспринимает A как аргумент. Если общий класс отмечен как ковариантный, с C [+ A], то A не может быть типом любого аргумента любой (общедоступной) подпрограммы C, и компилятор будет применять это.
Но стек в примере отличается. Мы имели бы def push(a: A): Stack[A]
. Если вы вызываете push
, вы получаете новый стек, а исходный стек остается неизменным, он по-прежнему является правильным Stack [Dog], что бы ни было нажато. Если мы делаем
val newStack = dogs.push(ostrich)
dogs
по-прежнему остается неизменным и остается Stack[Dog]
. Очевидно, что newStack
нет. Это также не Stack[Ostrich]
, поскольку он также содержит собак, которые были (и остаются) в исходном стеке. Но это было бы правильным Stack[Animal]
. Если кто-то подталкивает кошку, было бы точнее сказать, что это Stack[Mammal]
(будучи также стаей животных). Если кто-то нажимает 12
, это будет только Stack[Any]
, единственный общий супертип Dog
и Integer
. Проблема заключается в том, что компилятор не знает, что этот вызов безопасен и не допустит аргумент a: A
в def push(a: A): Stack[A]
, если Stack
отмечен ковариантным. Если бы он остановился там, ковариантный стек был бы бесполезен, потому что в нем не было бы возможности вносить в него значения.
Подпись решает проблему:
def push[B >: A](elem: B): Stack[B]
Если B
является предком A
, при добавлении B
получается Stack[B]
. Поэтому добавление Mammal
в Stack[Dog]
дает Stack[Mammal]
, добавление животного дает Stack[Animal]
, что прекрасно. Добавление собаки тоже нормально, A > : A истинно.
Это хорошо, но кажется слишком ограничительным. Что делать, если добавленный тип элемента не является предком A
? Например, что, если это потомок, например, dogs.push(goldenRetriever)
. Нельзя взять B = GoldenRetriever
, не имеет значения GoldenRetriever >: Dog
, а наоборот. Тем не менее, можно взять B = Собака в порядке. Предполагается, что параметр elem будет иметь тип Dog, мы можем пройти, конечно, мимо GoldenRetriever. Один получает стопку B, все еще стопку собак. И правильно, что B = GoldenRetriever
не разрешено. Результат был бы напечатан как Stack[GoldenRetriever]
, что было бы неправильно, потому что в стеке также могли содержаться ирландские сеттеры.
Как насчет острий? Ну Ostrich
не является ни супертипом, ни подтипом Dog
. Но так же, как можно добавить золотого ретривера, потому что это собака, и можно добавить собаку, страус - это животное, и можно добавить животное. Поэтому, принимая B = Animal > : Dog работает, и поэтому a при нажатии страуса получает Stack[Animal]
.
Создание ковариационной силы стека этой сигнатурой, более сложной, чем наивная push(a: A) : Stack[A]
. Но мы получаем рутину, которая является полностью гибкой, все может быть добавлено, а не только A
, и тем не менее, выводит результат как можно точнее. И фактическая реализация, за исключением объявлений типов, такая же, как и при использовании push(a: A)
.