Scala案例类副本并非总是与`_`存在类型一起使用 [英] Scala case class copy doesn't always work with `_` existential type

查看:67
本文介绍了Scala案例类副本并非总是与`_`存在类型一起使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试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

Scastie

到目前为止,我发现了两种解决方法:

So far I found two workarounds:

  • A中使Bar协变,然后问题隐藏起来,因为现在是Bar[_$1] <: Bar[Any]
  • Foo上定义一个copyId(newId: String) = copy(id = newId)方法并调用它,那么我们就不会在类型为Foo[_]的值上调用copy.
  • Make Bar covariant in A, then the problem hides itself because now 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[_].

但是,对于我的用例而言,这两个都不是真正可行的,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.v1foo.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案例类副本并非总是与`_`存在类型一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆