A case classes copy() method is supposed to make an identical copy of the instance, plus replacing any fields by name. This seems to fail when the case class has type parameters with manifests. The copy loses all knowledge of the types of its parameters.
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"
Why does Foo.copy lose the manifest on the parameters and how do I make it retain it?
Several Scala peculiarities interact to give this behavior. The first thing is that
Manifests are not only appended to the secret implicit parameter list on the constructor but also on the copy method. It is well known thatcase class Foo[+A : Manifest](a: A)is just syntactic sugar for
case class Foo[+A](a: A)(implicit m: Manifest[A])but this also affects the copy constructor, which would look like this
def copy[B](a: B = a)(implicit m: Manifest[B]) = Foo[B](a)(m)All those
implicit ms are created by the compiler and sent to the method through the implicit parameter list.This would be fine as long as one was using the
copymethod in a place where the compiler knewFoos type parameter. For example, this will work outside of the Bar class:This works because the compiler infers that
foois aFoo[Int]so it knows thatfoo.ais anIntso it can callcopylike this:val aCopy = foo.copy()(manifest[Int]())(Note that
manifest[T]()is a function that creates a manifest representation of the typeT, e.g.Manifest[T]with a capital “M”. Not shown is the addition of the default parameter intocopy.) It also works within theFooclass because it already has the manifest that was passed in when the class was created. It would look something like this:In the original example, however, it fails in the
Barclass because of the second peculiarity: while the type parameters ofBarare known within theBarclass, the type parameters of the type parameters are not. It knows thatAinBaris aFooor aSubFooorSubSubFoo, but not if it is aFoo[Int]or aFoo[String]. This is, of course, the well-known type erasure problem in Scala, but it appears as a problem here even when it doesn’t seem like the class is doing anything with the type offoos type parameter. But it is, remember there is a secret injection of a manifest every timecopyis called, and those manifests overwrite the ones that were there before. Since theBarclass has no idea was the type parameter offoois, it just creates a manifest ofAnyand sends that along like this:def fooCopy = foo.copy()(manifest[Any])If one has control over the
Fooclass (e.g. it’s notList) then one workaround it by doing all the copying over in the Foo class by adding a method that will do the proper copying, likelocalCopyabove, and return the result:Another solution is to add
Foos type parameter as a manifested type parameter ofBar:But this scales poorly if class hierarchy is large, (i.e. more members have type parameters, and those classes also have type parameters) since every class would have to have the type parameters of every class below it. It also seems to make the type inference freak out when trying to construct a
Bar: