问题描述
我正在尝试copy()
一个具有param类型的Scala case类.在呼叫站点,值的类型为Foo[_]
.
I'm trying to copy()
a Scala case class which has a type param. At the call site, the type of the value is 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]
的成员,它将不再编译:
But if I add another member of type Bar[A]
, it doesn't compile anymore:
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
到目前为止,我发现了两种解决方法:
So far I found two workarounds:
- 在
A
中使Bar
协变,然后问题隐藏起来,因为现在是Bar[_$1] <: Bar[Any]
- 在
Foo
上定义一个copyId(newId: String) = copy(id = newId)
方法并调用它,那么我们就不会在类型为Foo[_]
的值上调用copy
.
- Make
Bar
covariant inA
, then the problem hides itself because nowBar[_$1] <: Bar[Any]
- Define a
copyId(newId: String) = copy(id = newId)
method onFoo
and call that instead, then we aren't callingcopy
on a value of typeFoo[_]
.
但是,对于我的用例而言,这两个都不是真正可行的,Bar
应该是不变的,并且我在Foo[_]
实例上有太多不同的copy
调用,无法为它们全部创建copyThisAndThat
方法.
However, neither of those are really feasible for my use case, Bar
should be invariant, and I have too many different copy
calls on Foo[_]
instances to make copyThisAndThat
methods for them all.
我猜我真正的问题是,为什么Scala会这样?似乎是一个错误tbh.
I guess my real question is, why is Scala behaving this way? Seems like a bug tbh.
推荐答案
编译器处理命名参数和默认参数后,调用变为
After the compiler handles named and default parameters, the calls become
foo.copy("foo1.1", foo.name, foo.v1)
和
foo.copy("foo1.1", foo.name, foo.v1, foo.v2)
分别.或者,如果您将参数替换为类型,
respectively. Or, if you replace the parameters with types,
foo.copy[?](String, String, Bar[_])
和
foo.copy[?](String, String, Bar[_], Bar[_])
?
是必须推断的copy
的类型参数.在第一种情况下,编译器基本上会说"?
是Bar[_]
的类型参数,即使我不知道这是什么".
?
is the type parameter of copy
which has to inferred. In the first case the compiler basically says "?
is the type parameter of Bar[_]
, even if I don't know what that is".
在第二种情况下,两个Bar[_]
的类型参数必须确实相同,但是在编译器推断?
时,该信息已丢失;它们只是Bar[_]
,而不是Bar[foo's unknown type parameter]
之类的东西.所以"?
是第一个Bar[_]
的类型参数,即使我不知道那是什么".无法工作,因为据编译器所知,第二个Bar[_]
可能不同.
In the second case the type parameters of two Bar[_]
must really be the same, but that information is lost by the time the compiler is inferring ?
; they are just Bar[_]
, and not something like Bar[foo's unknown type parameter]
. So e.g. "?
is the type parameter of first Bar[_]
, even if I don't know what that is" won't work because so far as the compiler knows, the second Bar[_]
could be different.
在遵循语言规范的意义上,这不是错误;并更改规范以允许这样做将花费大量精力,并使它和编译器更加复杂.在这种相对罕见的情况下,这可能不是一个很好的权衡.
It isn't a bug in the sense that it follows the language specification; and changing the specification to allow this would take significant effort and make both it and the compiler more complicated. It may not be a good trade-off for such a relatively rare case.
另一种解决方法是使用类型变量模式为_
临时命名:
Another workaround is to use type variable pattern to temporarily give a name to _
:
foo match { case foo: Foo[a] => foo.copy(id = "foo1.1") }
编译器现在看到foo.v1
和foo.v2
都是Bar[a]
,因此copy
的结果是Foo[a]
.离开case
分支后,它将变为Foo[_]
.
The compiler now sees that foo.v1
and foo.v2
are both Bar[a]
and so the result of copy
is Foo[a]
. After leaving the case
branch it becomes Foo[_]
.
这篇关于Scala案例类副本并非总是与`_`存在类型一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!