Scala: how to make an instance of the case class to store manifest information

The case copy() method is supposed to make an identical copy of the instance plus replace any fields by name. This seems to crash when the case class has type parameters with manifests. A copy loses all knowledge about 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 will Foo.copy lose the parameter manifest and how to save it?

+8
scala type-erasure manifest type-parameter
source share
2 answers

Several Scala features interact to give this behavior. Firstly, Manifest not only added to the secret list of implicit parameters in the constructor, but also in 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 it also affects the copy constructor, which will look like this:

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

All those implicit m are created by the compiler and sent to the method through a list of implicit parameters.

It would be nice if one used the copy method in the place where the compiler knew a parameter of type Foo . For example, this will work outside the Bar class:

 val foo = Foo(1) val aCopy = foo.copy() println(aCopy.myManifest) // Prints "Int" 

This works because the compiler reports that Foo is Foo[Int] , so it knows that foo.a is 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 type T , for example Manifest[T] with a capital of "M". Not shown, adding a default parameter to copy .) It also works in the Foo class because it already has a manifest that was passed when the class was created. It will 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" 

However, in the original example, it fails in the Bar class due to the second peculiarity: while Bar type parameters are known in the Bar class, type parameter parameters are not type, He knows that A in Bar is Foo or SubFoo or SubSubFoo , but not if it's Foo[Int] or Foo[String] . This, of course, is a well-known type erasure problem in Scala, but here the problem is here, even if the class doesn't seem to do anything with the type parameter of type Foo . But this, remember, there is a secret injection of the manifest every time copy is called, and this shows a rewriting of those that were there before. Since the Bar class has no idea if there was a parameter of type Foo , it simply creates an Any manifest and sends it as follows:

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

If someone has control over the Foo class (for example, this is not a List ), then the workaround is to do all the copying in the Foo class by adding a method that will perform the correct copy, for example 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 a parameter of type Foo as an expressed parameter of type Bar :

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

But this does not scale well if the class hierarchy is large (i.e. more members have type parameters, and these classes also have type parameters), since each class must have type parameters of each class below. It looks like when trying to create a Bar :

 val bar = Bar(Foo(1)) // Does not compile val bar = Bar[Foo[Int], Int](Foo(1)) // Compiles 
+15
source share

You have identified two problems. The first problem is the issue of erasing the type inside Bar , where Bar does not know the type of the manifest Foo . I personally used the localCopy workaround you suggested.

The second problem is that another implicit is covertly injected into copy . This problem is solved by explicitly transferring the value again to 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 that the copy retained the Int manifest, but the types fooCopy and res2 equal to Manifest[Any] due to erasure.

Since I needed access to hidden evidence for copy , I had to use the explicit implicit (hah) syntax instead of the context-related syntax. But using 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 does the context-related syntax work and there is no explicit implicit ? I dug and found a solution to the problem: @uncheckedVariance annotation.

UPDATE

I dug up a few more and found that in Scala 2.10, the case classes were changed to only copy the fields from the first parameter list to copy() .

Martin says: case class ness is provided only by the first argument; the list of others should not be copied.

See https://issues.scala-lang.org/browse/SI-5009 for more details on this change.

+1
source share

All Articles