Контравариантные аргументы метода в Scala - PullRequest
1 голос
/ 28 июня 2019

Верхний ответ на этот вопрос объясняет, почему аргументы метода противоречивы.Автор говорит, что если это скомпилируется:

case class ListNode[+T](h: T, t: ListNode[T]) {
  def head: T = h
  def tail: ListNode[T] = t
  def prepend(elem: T): ListNode[T] =
    ListNode(elem, this)
}

Тогда все будет в порядке:

val list1: ListNode[String] = ListNode("abc", null)
val list2: ListNode[Any] = list1  // list2 and list1 are the same thing
val list3: ListNode[Int] = list2.prepend(1) // def prepend(elem: T): ListNode[T]

Обратите внимание, что я удалил последний ряд исходного фрагмента.

Мой мыслительный процесс выглядит следующим образом:

  1. В строке 1 ListNode[String] с именем list1 назначается новый ListNode(someString, null).Ничего странного здесь нет.list1 не является ListNode[String].
  2. В строке 2 ListNode[Any] назначается list1.Это нормально, потому что ListNode является ковариантным, а Any является супертипом String.
  3. В строке 3 вызывается метод prepend для list2.Так как list2 является ListNode[Any], list2.prepend() должен возвращать ListNode[Any].Но результат вызова метода присваивается ListNode[Int].Это не может быть скомпилировано, потому что Int является подтипом из Any и ListNode НЕ противоречиво!

Я что-то неправильно понял?Как автор может утверждать, что это когда-нибудь скомпилируется?

Ответы [ 2 ]

0 голосов
/ 28 июня 2019

Давайте рассмотрим, что произойдет, если мы определим как показано ниже:

case class ListNode[+T](h: T, t: ListNode[T]) {
  def head: T = h
  def tail: ListNode[T] = t
  def prepend(elem: T): ListNode[T] =
    ListNode(elem, this)
}

Если вы посмотрите на метод prepend, он принимает T (который является параметром типа ListNode) в качестве параметра и возвращает ListNode[T], все как есть. Теперь давайте уточним использование:

val list1: ListNode[String] = ListNode("abc", null)

В вышеприведенном случае String является супертипом null, а поскольку ListNode определено covarient, это правильно.

val list2: ListNode[Any] = list1

В приведенном выше втором случае ListNode[String] присваивается ListNode[Any], поскольку Any является супертипом String, что также является правильным.

val list3: ListNode[Int] = list2.prepend(1)

Наконец, в вышеприведенном третьем случае мы добавляем 1, то есть Int. Если вы посмотрите на метод prepend(elem: T): ListNode[T], мы передаем Int как тип значения elem и его возвращаемое ListNode типа T; в этом случае ListNode типа Int. Следовательно, значение, возвращаемое при вызове list2.prepend(1), имеет тип ListNode[Int]. Таким образом, приведенное выше выполнение для list3 также возможно (и теоретически исправлено).

Однако в scala вы не можете определить def prepend(elem: T): ListNode[T] в случае типа covariant ( вы получите ошибку компиляции ) и, следовательно, вы никогда не сможете выполнить val list3: ListNode[Int] = list2.prepend(1). Но вместо этого вы можете использовать нижние границы, как показано ниже:

case class ListNode[+T](h: T, t: ListNode[T]) {
  def head: T = h
  def tail: ListNode[T] = t
  def prepend[U<:T](elem: U): ListNode[U] =
    ListNode[U](elem, this)
}
0 голосов
/ 28 июня 2019

Рассмотрим метод prepend в отдельности:

def prepend(elem: T): ListNode[T]

Эта подпись означает, что если вы дадите ей T, вы получите ListNode[T]. Если вы дадите ему Int, вы получите ListNode[Int], поэтому задание, которое вы задаете вопрос, является совершенно действительным.

Также помните, что автор говорит, что эта версия ListNode не не компилируется, поэтому это гипотетическая ситуация.

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