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 Manifest
s are not only appended to the secret implicit parameter list on the constructor but also on the copy method. It is well known that
case 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 m
s 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 copy
method in a place where the compiler knew Foo
s type parameter. For example, this will work outside of the Bar class:
val foo = Foo(1)
val aCopy = foo.copy()
println(aCopy.myManifest) // Prints "Int"
This works because the compiler infers that foo
is a Foo[Int]
so it knows that foo.a
is an Int
so it can call copy
like this:
val aCopy = foo.copy()(manifest[Int]())
(Note that manifest[T]()
is a function that creates a manifest representation of the type T
, e.g. Manifest[T]
with a capital "M". Not shown is the addition of the default parameter into copy
.) It also works within the Foo
class because it already has the manifest that was passed in when the class was created. It would look something like this:
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"
In the original example, however, it fails in the Bar
class because of the second peculiarity: while the type parameters of Bar
are known within the Bar
class, the type parameters of the type parameters are not. It knows that A
in Bar
is a Foo
or a SubFoo
or SubSubFoo
, but not if it is a Foo[Int]
or a Foo[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 of foo
s type parameter. But it is, remember there is a secret injection of a manifest every time copy
is called, and those manifests overwrite the ones that were there before. Since the Bar
class has no idea was the type parameter of foo
is, it just creates a manifest of Any
and sends that along like this:
def fooCopy = foo.copy()(manifest[Any])
If one has control over the Foo
class (e.g. it's not List
) then one workaround it by doing all the copying over in the Foo class by adding a method that will do the proper copying, like localCopy
above, and return the result:
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"
Another solution is to add Foo
s type parameter as a manifested type parameter of Bar
:
case class Bar[A <: Foo[B], B : Manifest](foo: A) {
def fooCopy = foo.copy()
}
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
:
val bar = Bar(Foo(1)) // Does not compile
val bar = Bar[Foo[Int], Int](Foo(1)) // Compiles
There are two problems as you identified. The first problem is the type erasure problem inside of Bar
, where Bar
doesn't know the type of Foo
's manifest. I would personally use the localCopy
workaround you suggested.
The second issue is that another implicit is being secretly injected into copy
. That issue is resolved by explicitly passing the value again into the copy
. For example:
scala> case class Foo[+A](a: A)(implicit val m: Manifest[A @uncheckedVariance])
defined class Foo
scala> case class Bar[A <: Foo[Any]](foo: A) {
| def fooCopy = foo.copy()(foo.m)
| }
defined class Bar
scala> val foo = Foo(1)
foo: Foo[Int] = Foo(1)
scala> val bar = Bar(foo)
bar: Bar[Foo[Int]] = Bar(Foo(1))
scala> bar.fooCopy.m
res2: Manifest[Any] = Int
We see the copy has kept the Int
manifest but the type of fooCopy
and res2
is Manifest[Any]
due to erasure.
Because I needed access to the implicit evidence to do the copy
I had to use the explicit implicit
(hah) syntax instead of the context bound syntax. But using the explicit syntax caused errors:
scala> case class Foo[+A](a: A)(implicit val m: Manifest[A])
<console>:7: error: covariant type A occurs in invariant position in type => Manifest[A] of value m
case class Foo[+A](a: A)(implicit val m: Manifest[A])
^
scala> case class Foo[+A](a: A)(implicit val m: Manifest[_ <: A])
defined class Foo
scala> val foo = Foo(1)
<console>:9: error: No Manifest available for Int.
WTF? How come the context bound syntax works and the explicit implicit
doesn't? I dug around and found a solution to the problem: the @uncheckedVariance
annotation.
UPDATE
I dug around some more and found that in Scala 2.10 case classes have been changed to only copy the fields from the first parameter list in copy()
.
Martin says: case class ness is only bestowed on the first argument list the rest should not be copied.
See the details of this change at https://issues.scala-lang.org/browse/SI-5009.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With