Являются ли присваивания неявно ковариантными?

Должно быть, это очень простое недоразумение с моей стороны. Похоже, что назначения параметрических типов являются ковариантными без каких-либо указаний с моей стороны, что это то, что я хотел бы. Я вставляю код Scala для краткости, но в Java он ведет себя точно так же.

class Pet
class Fish extends Pet
class Guppy extends Fish
case class Box[T](value: T)
val guppyBox: Box[Fish] = Box(new Guppy()) // Mysteriously, this works.

Экземпляр типа X может быть назначен val типа Y только в том случае, если Y является супертипом X. В моем случае это потребовало бы, чтобы Box был ковариантным, чего я не говорил.

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

  def unboxFish(fish: Box[Fish]) = ???

  unboxFish(Box(new Guppy()))       // Oddly, compiles ok
  val guppyBox2 = Box(new Guppy())
  unboxFish(guppyBox2)              // The compilation error I'd expect.

Любая помощь очень ценится!

Guppy — это Fish, поэтому вы можете создать Box[Fish], значение которого равно Guppy. Вот почему Box может быть эффективно ковариантным.

Luis Miguel Mejía Suárez 25.01.2023 20:45

Это не то, что означает ковариант. Ковариант был бы, если бы Box[Guppy] расширил Box[Fish], чего не происходит. Здесь происходит то, что вы передаете значение функции, когда тип значения расширяет тип параметра функции. Вы всегда можете сделать это, независимо от дисперсии.

Edward Peters 26.01.2023 00:24

@LuisMiguelMejíaSuárez Да, говорить, что это ковариация (эффективно?), сбивает с толку. Ковариация разная. Box[Guppy] не является подтипом Box[Fish], мы не можем присвоить значение типа Box[Guppy] переменной типа Box[Fish]. Возможность использовать Guppy там, где ожидается Fish (Box[Fish](..here..)), — это просто принцип Лисков.

Dmytro Mitin 26.01.2023 00:50

@DmytroMitin Я не говорил, что Box был ковариантным, я сказал, что Box можно сделать ковариантным, потому что все, что делает Box, это содержит значение, поэтому это просто простая оболочка над любым значением. Таким образом, имеет смысл, что такая оболочка может следовать отношениям подтипа того, что она обертывает. - Опять же, я не говорю, что это так, и я не говорю, что все общие типы или оболочки могут.

Luis Miguel Mejía Suárez 26.01.2023 00:59

@EdwardPeters то, что вы сказали, в основном правильно. Однако обратите внимание, что вы используете формулировку слова «расширяет» для классов, тогда как и дисперсия, и Лисков относятся к типам. - Кроме того, "всегда можно сделать это" в языке с подтипами; не все языки имеют подтипы.

Luis Miguel Mejía Suárez 26.01.2023 01:01

@LuisMiguelMejíaSuárez Я вижу, вы говорите не о том, что Box является ковариантным, вы говорите, что T в case class Box[T](value: T) находится в ковариантном положении scala-lang.org/files/archive/spec/2.13/…

Dmytro Mitin 26.01.2023 01:14
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
6
89
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Box не является ковариантным. Здесь происходит то, что Box(new Guppy()) необходимо вывести параметр типа для Box, а вывод зависит от контекста. Когда вы делаете

val guppyBox2 = Box(new Guppy())

он выводит параметр типа как Guppy, но когда вы это делаете

val guppyBox: Box[Fish] = Box(new Guppy())

компилятор знает, что RHS должен быть Box[Fish], поэтому он делает вывод, что параметр типа должен быть Fish вместо Guppy, как если бы вы написали

val guppyBox: Box[Fish] = Box[Fish](new Guppy())
Ответ принят как подходящий

В Scala вывод типов идет не только справа налево

val guppyBox: Box[??] = Box[Something](...)

но и слева направо

val guppyBox: Box[Something] = Box[??](...)

(так что это двунаправленный).

Итак, в

val guppyBox: Box[Fish] = Box(new Guppy())

он же

val guppyBox: Box[Fish] = Box[??](new Guppy())

параметр типа ?? подразумевается как Fish.

когда нужен явный тип при объявлении переменной в scala?

Но Box теперь не является ковариантным. Box[Guppy] не является подтипом Box[Fish]

implicitly[Box[Guppy] <:< Box[Fish]] // doesn't compile

Вы не можете присвоить значение типа Box[Guppy] переменной типа Box[Fish]

val guppyBox: Box[Fish] = Box[Guppy](new Guppy()) // doesn't compile

Спасибо, @DmytroMitin. Возможно, я запутал вопрос всеми этими предположениями о дисперсии, и реальная проблема, как вы указываете, заключается в том, как компилятору удалось неявно преобразовать Box(new Guppy()) в Box[Fish], когда Box[Fish] и Box[Guppy] не связаны. Кое-что о выводе типов параметрических типов, о которых мне нужно подумать.

Igor Urisman 26.01.2023 17:48

В вашем примере Box[Fish] является подтипом Box[Pet], потому что Fish является подтипом Pet. Это называется параметризацией ковариантного типа и разрешено в Scala и Java. Вот почему вы можете присвоить Box[Guppy] переменной типа Box[Fish]. Что касается второй части примера, когда вы передаете Box[Guppy] в качестве аргумента методу unboxFish, компилятор автоматически преобразует его в Box[Fish], так как метод ожидает Box[Fish] в качестве аргумента. Однако, когда вы пытаетесь передать guppyBox2 методу unboxFish, компилятор не знает, что это Box[Guppy], потому что тип переменной стирается во время выполнения, поэтому он не может преобразовать его в Box[Fish]. . Вот почему вы получаете ошибку компиляции.

1) Box[Fish] не является подтипом Box[Pet]. 2) Стирание типа не имеет отношения к выводу типа, потому что первое происходит во время фазы erasure, а второе происходит намного раньше, на фазе typertypelevel.org/scala/docs/phases.html

Dmytro Mitin 29.01.2023 00:29

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