Scala копия класса case не всегда работает с экзистенциальным типом `_` - PullRequest
6 голосов
/ 13 июля 2020

Я пытаюсь использовать copy() класс Scala case, который имеет параметр типа. На сайте вызова тип значения - Foo[_].

Это компилируется, как ожидалось:

case class Foo[A](id: String, name: String, v1: Bar[A])
case class Bar[A](v: A)

val foo: Foo[_] = Foo[Int]("foo1", "Foo 1", Bar[Int](1))

foo.copy(id = "foo1.1")

Но если я добавлю еще один член типа Bar[A], он не будет ' t больше компилировать:

case class Foo[A](id: String, name: String, v1: Bar[A], v2: Bar[A])
case class Bar[A](v: A)

val foo: Foo[_] = Foo[Int]("foo1", "Foo 1", Bar[Int](1), Bar[Int](2))

foo.copy(id = "foo1.1") // compile error, see below
type mismatch;
 found   : Playground.Bar[_$1]
 required: Playground.Bar[Any]
Note: _$1 <: Any, but class Bar is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
Error occurred in an application involving default arguments

Scast ie

На данный момент я нашел два обходных пути:

  • Make Bar ковариантно в A, тогда проблема скрывается сама собой, потому что теперь Bar[_$1] <: Bar[Any]
  • Определите метод copyId(newId: String) = copy(id = newId) на Foo и вызовите его вместо этого, тогда мы не будем вызывать copy на значение типа Foo[_].

Однако ни один из них не подходит для моего варианта использования, Bar не должен быть инвариантным, и у меня слишком много разных copy вызовов Foo[_] экземпляров, чтобы создать copyThisAndThat методов для всех.

Думаю, мой настоящий вопрос в том, почему Scala так себя ведет? Похоже на ошибку tbh.

1 Ответ

6 голосов
/ 13 июля 2020

После того, как компилятор обработает именованные параметры и параметры по умолчанию, вызовы становятся

foo.copy("foo1.1", foo.name, foo.v1)

и

foo.copy("foo1.1", foo.name, foo.v1, foo.v2)

соответственно. Или, если вы замените параметры типами,

foo.copy[?](String, String, Bar[_])

и

foo.copy[?](String, String, Bar[_], Bar[_])

? - это параметр типа copy, который должен быть выведен. В первом случае компилятор в основном говорит: «? - параметр типа Bar[_], даже если я не знаю, что это такое».

Во втором случае параметры типа двух Bar[_] действительно должно быть таким же, но эта информация теряется к тому времени, когда компилятор делает вывод ?; они просто Bar[_], а не что-то вроде Bar[foo's unknown type parameter]. Так, например, «? является параметром типа первого Bar[_], даже если я не знаю, что это» не будет работать, потому что, насколько известно компилятору, второй Bar[_] может быть другим.

Это не ошибка в том смысле, что оно следует спецификации языка; и изменение спецификации, чтобы разрешить это, потребует значительных усилий и усложнит как его, так и компилятор. Возможно, это не лучший компромисс для такого относительно редкого случая.

Другой обходной путь - использовать шаблон переменной типа для временного присвоения имени _:

foo match { case foo: Foo[a] => foo.copy(id = "foo1.1") }

компилятор теперь видит, что foo.v1 и foo.v2 оба являются Bar[a], поэтому результат copy равен Foo[a]. После выхода из ветки case он становится Foo[_].

...