Nik*_*ita 6 generics scala type-inference existential-type case-class
我正在尝试copy()一个具有类型参数的 Scala 案例类。在呼叫站点,该值的类型为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")
Run Code Online (Sandbox Code Playgroud)
但是,如果我添加另一个 type 成员Bar[A],它将不再编译:
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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
到目前为止,我发现了两种解决方法:
Bar协变 in A,那么问题就隐藏了,因为现在Bar[_$1] <: Bar[Any]copyId(newId: String) = copy(id = newId)在 on 上定义一个方法Foo并调用它,然后我们就不会调用copytype 的值Foo[_]。然而,对于我的用例来说,这些都不是真的可行,Bar应该是不变的,而且我copy对Foo[_]实例有太多不同的调用来copyThisAndThat为它们创建方法。
我想我真正的问题是,为什么 Scala 会这样?似乎是一个错误 tbh。
编译器处理命名参数和默认参数后,调用变为
foo.copy("foo1.1", foo.name, foo.v1)
Run Code Online (Sandbox Code Playgroud)
和
foo.copy("foo1.1", foo.name, foo.v1, foo.v2)
Run Code Online (Sandbox Code Playgroud)
分别。或者,如果用类型替换参数,
foo.copy[?](String, String, Bar[_])
Run Code Online (Sandbox Code Playgroud)
和
foo.copy[?](String, String, Bar[_], Bar[_])
Run Code Online (Sandbox Code Playgroud)
?是copy必须推断的类型参数。在第一种情况下,编译器基本上说“?是 的类型参数Bar[_],即使我不知道那是什么”。
在第二种情况下,两个的类型参数Bar[_]必须确实相同,但是在编译器推断时该信息丢失了?;他们只是Bar[_],而不是类似的东西Bar[foo's unknown type parameter]。因此,例如“?是 first 的类型参数Bar[_],即使我不知道那是什么”也不起作用,因为据编译器所知,第二个Bar[_]可能不同。
从它遵循语言规范的意义上说,它不是一个错误;并更改规范以允许这样做需要付出巨大的努力,并使它和编译器都变得更加复杂。对于这种相对罕见的情况,这可能不是一个好的权衡。
另一种解决方法是使用类型变量模式临时命名_:
foo match { case foo: Foo[a] => foo.copy(id = "foo1.1") }
Run Code Online (Sandbox Code Playgroud)
编译器现在看到foo.v1and foo.v2are bothBar[a]和copyis的结果Foo[a]。离开case分支后,它变为Foo[_]。