Scala:如何使案例类复制保留清单信息

drh*_*gen 8 scala manifest type-erasure type-parameter

案例类copy()方法应该生成实例的相同副本,并且按名称替换任何字段.当case类具有带有清单的类型参数时,这似乎失败了.该副本失去了对其参数类型的所有知识.

case class Foo[+A : Manifest](a: A) {
  // Capture manifest so we can observe it
  // A demonstration with collect would work equally well
  def myManifest = implicitly[Manifest[_ <: A]]
}

case class Bar[A <: Foo[Any]](foo: A) {
  // A simple copy of foo
  def fooCopy = foo.copy()
}

val foo = Foo(1)
val bar = Bar(foo)

println(bar.foo.myManifest)     // Prints "Int"
println(bar.fooCopy.myManifest) // Prints "Any"
Run Code Online (Sandbox Code Playgroud)

为什么会Foo.copy丢失参数上的清单以及如何保留它?

drh*_*gen 15

几个Scala特性相互作用以产生这种行为.第一件事是Manifests不仅附加到构造函数上的秘密隐式参数列表,还附加到复制方法.众所周知

case class Foo[+A : Manifest](a: A)

只是语法糖

case class Foo[+A](a: A)(implicit m: Manifest[A])

但这也会影响复制构造函数,它看起来像这样

def copy[B](a: B = a)(implicit m: Manifest[B]) = Foo[B](a)(m)

所有这些implicit m都是由编译器创建的,并通过隐式参数列表发送给方法.

只要copy在编译器知道Foos类型参数的地方使用该方法,这就没问题.例如,这将在Bar类之外工作:

val foo = Foo(1)
val aCopy = foo.copy()
println(aCopy.myManifest) // Prints "Int"
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为编译器推断它foo是一个Foo[Int]所以它知道这foo.a是一个Int所以它可以copy像这样调用:

val aCopy = foo.copy()(manifest[Int]())

(注意,这manifest[T]()是一个创建类型的清单表示的函数T,例如Manifest[T],使用大写"M".未显示是将默认参数添加到copy.)它也可以在Foo类中工作,因为它已经具有传递的清单在课程创建时.它看起来像这样:

case class Foo[+A : Manifest](a: A) {
  def myManifest = implicitly[Manifest[_ <: A]]

  def localCopy = copy()
}

val foo = Foo(1)
println(foo.localCopy.myManifest) // Prints "Int"
Run Code Online (Sandbox Code Playgroud)

然而,在原始示例中,Bar由于第二个特性,它在类中失败:虽然类中的类型参数Bar是已知的Bar,但类型参数的类型参数却不是.它知道Ain Bar是a Foo或a,SubFoo或者SubSubFoo不知道是a Foo[Int]还是a Foo[String].当然,这是Scala中众所周知的类型擦除问题,但它在这里看起来是一个问题,即使它看起来似乎没有使用foos类型参数的类做任何事情.但是,记住,每次copy调用时都会秘密注入一个清单,而那些清单会覆盖之前存在的清单.由于Bar类不知道foois 的类型参数,它只是创建一个清单Any并像这样发送:

def fooCopy = foo.copy()(manifest[Any])

如果一个人控制了这个Foo类(例如它不是List),那么通过添加一个能够进行正确复制的方法(localCopy如上所述),通过在Foo类中进行所有复制来解决它,并返回结果:

case class Bar[A <: Foo[Any]](foo: A) {
  //def fooCopy = foo.copy()
  def fooCopy = foo.localCopy
}

val bar = Bar(Foo(1))
println(bar.fooCopy.myManifest) // Prints "Int"
Run Code Online (Sandbox Code Playgroud)

另一种解决方案是添加Foos类型参数作为显式类型参数Bar:

case class Bar[A <: Foo[B], B : Manifest](foo: A) {
  def fooCopy = foo.copy()
}
Run Code Online (Sandbox Code Playgroud)

但是如果类层次结构很大,这种扩展性很差(即更多成员具有类型参数,并且这些类也具有类型参数),因为每个类都必须具有其下的每个类的类型参数.在尝试构建一个类似的推理时,它似乎也会使类型推断变得异常Bar:

val bar = Bar(Foo(1)) // Does not compile

val bar = Bar[Foo[Int], Int](Foo(1)) // Compiles
Run Code Online (Sandbox Code Playgroud)