Что [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),

Как отличный обзор, смотрите страницу git @retronym

Другие вопросы по тегам