简体   繁体   English

Scala case class 副本并不总是适用于`_`存在类型

[英]Scala case class copy doesn't always work with `_` existential type

I'm trying to copy() a Scala case class which has a type param.我正在尝试copy()具有类型参数的 Scala 案例 class 。 At the call site, the type of the value is Foo[_] .在调用站点,值的类型是Foo[_]

This compiles as expected:这按预期编译:

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")

But if I add another member of type Bar[A] , it doesn't compile anymore:但是如果我添加另一个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[_$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

Scastie斯卡斯蒂

So far I found two workarounds:到目前为止,我找到了两种解决方法:

  • Make Bar covariant in A , then the problem hides itself because now Bar[_$1] <: Bar[Any]使BarA中协变,然后问题就隐藏了,因为现在Bar[_$1] <: Bar[Any]
  • Define a copyId(newId: String) = copy(id = newId) method on Foo and call that instead, then we aren't calling copy on a value of type Foo[_] .在 Foo 上定义一个copyId(newId: String) = copy(id = newId)方法并调用它,然后我们不会对Foo Foo[_]类型的值调用copy

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.但是,对于我的用例来说,这些都不是真正可行的, Bar应该是不变的,而且我对Foo[_]实例有太多不同的copy调用,无法为它们创建copyThisAndThat方法。

I guess my real question is, why is Scala behaving this way?我想我真正的问题是,为什么 Scala 会这样? Seems like a bug tbh.似乎是一个错误。

After the compiler handles named and default parameters, the calls become编译器处理命名参数和默认参数后,调用变为

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

and

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

respectively.分别。 Or, if you replace the parameters with types,或者,如果您将参数替换为类型,

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

and

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

? is the type parameter of copy which has to inferred.是必须推断的copy的类型参数。 In the first case the compiler basically says " ? is the type parameter of Bar[_] , even if I don't know what that is".在第一种情况下,编译器基本上说“ ?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 ?在第二种情况下,两个Bar[_]的类型参数必须确实相同,但是在编译器推断时该信息会丢失? ; ; they are just Bar[_] , and not something like Bar[foo's unknown type parameter] .它们只是Bar[_] ,而不是Bar[foo's unknown type parameter]东西。 So eg " ? 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.因此,例如“ ?是第一个Bar[_]的类型参数,即使我不知道那是什么”将不起作用,因为据编译器所知,第二个Bar[_]可能不同。

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") }

The compiler now sees that foo.v1 and foo.v2 are both Bar[a] and so the result of copy is Foo[a] .编译器现在看到foo.v1foo.v2都是Bar[a] ,所以copy的结果是Foo[a] After leaving the case branch it becomes Foo[_] .离开case分支后,它变为Foo[_]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM