Scala case class 复制并不总是适用于`_`存在类型
Scala case class copy doesn't always work with `_` existential type
我正在尝试 copy()
一个具有类型参数的 Scala 案例 class。在调用站点,值的类型为 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]
类型的成员,它将不再编译:
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[_]
required: Playground.Bar[Any]
Note: _ <: 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
到目前为止我找到了两个解决方法:
- 使
Bar
在 A
中协变,然后问题就隐藏起来了,因为现在 Bar[_] <: Bar[Any]
- 在
Foo
上定义一个 copyId(newId: String) = copy(id = newId)
方法并调用它,这样我们就不会在 Foo[_]
.[=46= 类型的值上调用 copy
]
但是,对于我的用例来说,这些都不是真正可行的,Bar
应该是不变的,而且我对 Foo[_]
实例有太多不同的 copy
调用,使 copyThisAndThat
方法。
我想我真正的问题是,为什么 Scala 会这样?似乎是个错误。
编译器处理命名参数和默认参数后,调用变为
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[_]
.
我正在尝试 copy()
一个具有类型参数的 Scala 案例 class。在调用站点,值的类型为 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]
类型的成员,它将不再编译:
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[_]
required: Playground.Bar[Any]
Note: _ <: 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
到目前为止我找到了两个解决方法:
- 使
Bar
在A
中协变,然后问题就隐藏起来了,因为现在Bar[_] <: Bar[Any]
- 在
Foo
上定义一个copyId(newId: String) = copy(id = newId)
方法并调用它,这样我们就不会在Foo[_]
.[=46= 类型的值上调用copy
]
但是,对于我的用例来说,这些都不是真正可行的,Bar
应该是不变的,而且我对 Foo[_]
实例有太多不同的 copy
调用,使 copyThisAndThat
方法。
我想我真正的问题是,为什么 Scala 会这样?似乎是个错误。
编译器处理命名参数和默认参数后,调用变为
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[_]
.