Должно быть, это очень простое недоразумение с моей стороны. Похоже, что назначения параметрических типов являются ковариантными без каких-либо указаний с моей стороны, что это то, что я хотел бы. Я вставляю код 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.
Любая помощь очень ценится!
Это не то, что означает ковариант. Ковариант был бы, если бы Box[Guppy]
расширил Box[Fish]
, чего не происходит. Здесь происходит то, что вы передаете значение функции, когда тип значения расширяет тип параметра функции. Вы всегда можете сделать это, независимо от дисперсии.
@LuisMiguelMejíaSuárez Да, говорить, что это ковариация (эффективно?), сбивает с толку. Ковариация разная. Box[Guppy]
не является подтипом Box[Fish]
, мы не можем присвоить значение типа Box[Guppy]
переменной типа Box[Fish]
. Возможность использовать Guppy
там, где ожидается Fish
(Box[Fish](..here..)
), — это просто принцип Лисков.
@DmytroMitin Я не говорил, что Box
был ковариантным, я сказал, что Box
можно сделать ковариантным, потому что все, что делает Box
, это содержит значение, поэтому это просто простая оболочка над любым значением. Таким образом, имеет смысл, что такая оболочка может следовать отношениям подтипа того, что она обертывает. - Опять же, я не говорю, что это так, и я не говорю, что все общие типы или оболочки могут.
@EdwardPeters то, что вы сказали, в основном правильно. Однако обратите внимание, что вы используете формулировку слова «расширяет» для классов, тогда как и дисперсия, и Лисков относятся к типам. - Кроме того, "всегда можно сделать это" в языке с подтипами; не все языки имеют подтипы.
@LuisMiguelMejíaSuárez Я вижу, вы говорите не о том, что Box
является ковариантным, вы говорите, что T
в case class Box[T](value: T)
находится в ковариантном положении scala-lang.org/files/archive/spec/2.13/…
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]
не связаны. Кое-что о выводе типов параметрических типов, о которых мне нужно подумать.
В вашем примере 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
, а второе происходит намного раньше, на фазе typer
typelevel.org/scala/docs/phases.html
Как сейчас написано, ваш ответ неясен. Пожалуйста, отредактируйте , чтобы добавить дополнительные сведения, которые помогут другим понять, как это отвечает на заданный вопрос. Вы можете найти больше информации о том, как писать хорошие ответы в справочном центре.
Guppy
— этоFish
, поэтому вы можете создатьBox[Fish]
, значение которого равноGuppy
. Вот почемуBox
может быть эффективно ковариантным.