Что [B>: A] делает в Scala? - PullRequest
       5

Что [B>: A] делает в Scala?

31 голосов
/ 13 октября 2011

Что означает [B >: A] в Scala?И каковы эффекты?

Пример ссылки: 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 ]

39 голосов
/ 13 октября 2011

[B >: A] - нижняя граница типа.Это означает, что B является супертипом A.

Аналогично [B <: A] является верхней границей типа, что означает, что B ограничено, чтобы быть подтипом A.

В показанном вами примере вам разрешено помещать элемент типа B в стек, содержащий A элементов, но в результате получается стек B элементов.

На странице, где вы видели это, есть ссылка на другую страницу о нижних границах типа , которая включает пример, демонстрирующий эффект.

18 голосов
/ 14 октября 2011

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], который является другим типом), будучи ковариантным, это означает, что он увеличивается функция (с <:, отношение подтипа является порядком на типах). </p>

Это кажется правильным, но это не такой простой вопрос. Это не было бы так, с помощью процедуры 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, он получает новый стек, а исходный стек остается неизменным, это все равно правильный стек [собака], что бы ни было сдано. Если мы сделаем

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>: Dog работает, и поэтому при нажатии на страуса вы получаете Stack[Animal].

Создание стека ковариантного форсирования этой сигнатуры, более сложное, чем наивный push(a: A) : Stack[A].Но мы получаем процедуру, которая является абсолютно гибкой, можно добавить что угодно, не только A, и, тем не менее, печатать результат с максимальной точностью.И фактическая реализация, за исключением объявлений типов, такая же, как была бы с push(a: A).

3 голосов
/ 14 октября 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...