Ответ 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).