Реализация метода внутри параметризованного класса Scala с ковариантным типом - PullRequest
0 голосов
/ 13 января 2019

Я прочитал несколько учебных пособий, включая основную документацию по Scala, касающуюся сигнатур методов ковариантных типов. Предположим, у меня есть следующий абстрактный класс:

abstract class List[+A] {

  def head: A
  def tail: List[A]
  def isEmpty: Boolean
  def add[B >: A](element: B): List[B]
  protected def printElements: String

  override def toString: String = "[" + printElements + "]"

}

Мой вопрос касается подписи метода add(). Почему это нужно так декларировать? Мы передаем параметр, который является супертипом A. Какую проблему это решает? Я пытаюсь понять это на интуитивном уровне.

Ответы [ 3 ]

0 голосов
/ 13 января 2019

Когда вы объявляете +A, вы говорите, что, например, List[String] расширяет List[Object]. Теперь представьте себе:

val ls: List[Object] = List[String]() // Legal because of covariance
ls.add(1) // Adding an int to a list of String?

Это допустимо только в том случае, если тип Списка может быть расширен для включения произвольных Объектов, что и делает ваша подпись добавления. В противном случае существование add(a: A) будет означать несоответствие в системе типов.

0 голосов
/ 13 января 2019

Формальное объяснение

Учитывая

abstract class List[+A] {
  def add(element: A): List[A]
}

"Эта программа не компилируется, потому что параметр element в add имеет тип A, который мы объявили covariant . Это не работает, потому что функции являются контравариантными в своих типах параметров и ковариантными в их типах результатов. Чтобы исправить это, нам нужно перевернуть дисперсию типа параметр элемент в add.
Мы делаем это, вводя новый параметр типа B, который имеет A в качестве нижней границы типа".
- ссылка .

Интуитивное объяснение

В этом примере, если вы add что-то в List :
Это должен быть A - в этом случае List по-прежнему List[A].
Или это должен быть любой подтип из A - в этом случае элемент получает повышенный до A, а List остается List[A].
Или, если это другой тип B, тогда он ДОЛЖЕН быть супертипом из A - в этом случае List преобразуется в List[B]. (Примечание: поскольку Any - это просто супертип всего, в худшем случае List будет преобразован в List[Any]) .

0 голосов
/ 13 января 2019

Предположим, я хочу составить список целых чисел. И предположим, ради аргумента, что add реализован без обобщений.

def add(element: A): List[A]

Ради этого примера предположим, что у нас есть какой-то способ создания «пустого» списка.

def emptyList[A]: List[A] = /* some magic */

Теперь я хочу составить список целых чисел.

(1 to 10).foldRight(emptyList) { (x, acc) => acc.add(x) }

Oops! У нас есть проблемы! Когда я звоню emptyList, Scala собирается вывести самый общий тип , и, поскольку A является ковариантным, он будет предполагать Nothing. Это означает, что я просто попытался добавить целое число в список ничего. Мы могли бы исправить эту проблему с помощью явной сигнатуры типа,

(1 to 10).foldRight(emptyList[Int]) { (x, acc) => acc.add(x) }

Но, на самом деле, это не решает проблему. Это ничего не добавляет к удобочитаемости и просто требует от пользователя дополнительной работы. Реально я должен иметь возможность добавить номер в список ничего. Просто если я решу это сделать, я больше не смогу назвать это списком Nothing. Следовательно, если мы определим

def add[B >: A](element: B): List[B]

Теперь я могу начать с List[Nothing] и добавить к нему Int. То, что я выхожу, больше не List[Nothing]; это List[Int], но я могу это сделать. Если я возьму это List[Int] и приду позже и добавлю к нему String, я тоже могу это сделать, но теперь у меня есть практически бесполезный List[Any].

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