Что [B >: A] делает в Scala?
Что значит [B >: A]
значит в скале? И каковы последствия?
Пример ссылки: http://www.scala-lang.org/node/129
class Stack[+A] {
def push[B >: A](elem: B): Stack[B] = new Stack[B] {
override def top: B = elem
override def pop: Stack[B] = Stack.this
override def toString() = elem.toString() + " " + Stack.this.toString()
}
def top: A = error("no element on stack")
def pop: Stack[A] = error("no element on stack")
override def toString() = ""
}
object VariancesTest extends Application {
var s: Stack[Any] = new Stack().push("hello");
s = s.push(new Object())
s = s.push(7)
println(s)
}
3 ответа
[B >: A]
является нижней границей типа. Это означает, что B
вынужден быть супертипом A
,
так же [B <: A]
верхний предел типа, означающий, что B
ограничен, чтобы быть подтипом A
,
В примере, который вы показали, вы можете нажать элемент типа B
на стек, содержащий A
элементы, но в результате получается стек B
элементы.
Страница, на которой вы видели это, на самом деле имеет ссылку на другую страницу о нижних границах типа, которая включает пример, показывающий эффект.
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], который является другим типом), будучи ковариантным, это означает, что он увеличивается функция (с <:, отношение подтипа является порядком на типах).
Это кажется правильным, но это не такой простой вопрос. Это не было бы так, с помощью процедуры push, которая изменяет ее, добавляя новый элемент, то есть
def push(a: A): Unit
(пример другой, push возвращает новый стек, оставляя this
без изменений). Конечно, Стек [Собака] должен принимать только собак, которых толкают в него. В противном случае, это больше не будет стая собак. Но если мы примем, что это будет рассматриваться как стая животных, мы могли бы сделать
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
, каждый получает новый стек, и оригинальный стек остается неизменным, это все еще правильный стек [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 = Dog все в порядке. Если ожидается, что параметр elem будет типа Dog, мы можем передать GoldenRetriever. Каждый получает стог B, все еще стог собак. И это правильно, что B = GoldenRetriever
не было разрешено Результат был бы напечатан как Stack[GoldenRetriever]
, что было бы неправильно, потому что в стеке тоже могли быть ирландские сеттеры.
Как насчет страусов? Что ж Ostrich
не является ни супертипом, ни подтипом Dog
, Но так же, как можно добавить золотистого ретривера, потому что это собака, и можно добавить собаку, страус - это животное, и можно добавить животное. Итак, взяв B = Animal >: собака работает, и поэтому, когда толкаешь страуса, человек получает Stack[Animal]
,
Создание стека ковариантного заставить эту сигнатуру сложнее, чем наивный push(a: A) : Stack[A]
, Но мы получаем режим, который является абсолютно гибким, все может быть добавлено, не только A
и, тем не менее, печатает результат как можно точнее. И фактическая реализация, за исключением объявлений типов, такая же, как и при push(a: A)
,