Varias peculiaridades de Scala interactúan para dar este comportamiento. Lo primero es que Manifest
s no solo se anexan a la lista de parámetros implícitos secretos en el constructor, sino también en el método de copia. Es bien sabido que
case class Foo[+A : Manifest](a: A)
es el azúcar solo sintáctica para
case class Foo[+A](a: A)(implicit m: Manifest[A])
pero esto también afecta al constructor de copia, lo que se vería así
def copy[B](a: B = a)(implicit m: Manifest[B]) = Foo[B](a)(m)
Todos esos implicit m
s son creados por th e compilador y enviado al método a través de la lista de parámetros implícitos.
Esto estaría bien siempre que se utilizara el método copy
en un lugar donde el compilador conocía el parámetro de tipo Foo
s. Por ejemplo, esto va a funcionar fuera de la clase de barras:
val foo = Foo(1)
val aCopy = foo.copy()
println(aCopy.myManifest) // Prints "Int"
Esto funciona porque el compilador infiere que foo
es una Foo[Int]
por lo que sabe que foo.a
es un Int
por lo que se puede llamar copy
así:
val aCopy = foo.copy()(manifest[Int]())
(Tenga en cuenta que manifest[T]()
es una función que crea una representación manifiesta de tipo T
, por ejemplo Manifest[T]
con una "M" de capital. No se muestra es la de una Además del parámetro predeterminado en copy
. También funciona dentro de la clase Foo
porque ya tiene el manifiesto que se pasó cuando se creó la clase. Se vería algo como esto:
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"
En el ejemplo original, sin embargo, se produce un error en la clase Bar
debido a la segunda peculiaridad: mientras que los parámetros de tipo de Bar
son conocidos dentro de la clase Bar
, los parámetros de tipo de los parámetros de tipo no son. Sabe que A
en Bar
es Foo
o SubFoo
o SubSubFoo
, pero no si es Foo[Int]
o Foo[String]
. Este es, por supuesto, el conocido problema de borrado de tipos en Scala, pero aparece como un problema aquí, incluso cuando no parece que la clase esté haciendo algo con el tipo de parámetro de tipo foo
. Pero lo es, recuerde que hay una inyección secreta de un manifiesto cada vez que se llama copy
, y esos manifiestos sobrescriben los que estaban allí antes.Puesto que la clase Bar
no tiene idea era el parámetro de tipo de foo
es, simplemente crea un manifiesto de Any
y envía de que a lo largo de la siguiente manera:
def fooCopy = foo.copy()(manifest[Any])
Si uno tiene el control sobre la clase Foo
(por ejemplo, no es List
) entonces uno solución que haciendo toda la copia más en la clase Foo mediante la adición de un método que va a hacer la copia adecuada, como localCopy
anteriormente, y devolver el resultado:
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"
Otra solución es añadir parámetro de tipo Foo
s como un parámetro de tipo manifestada de Bar
:
case class Bar[A <: Foo[B], B : Manifest](foo: A) {
def fooCopy = foo.copy()
}
Pero esto escalas mal si jerarquía de clases es grande, (es decir, más miembros tienen parámetros de tipo, y esas clases también tienen parámetros de tipo) ya que cada clase debería tener los parámetros de tipo de cada clase debajo de ella. También parece que la inferencia de tipos monstruo hacia fuera cuando se trata de construir un Bar
:
val bar = Bar(Foo(1)) // Does not compile
val bar = Bar[Foo[Int], Int](Foo(1)) // Compiles
¡Buen trabajo, amigo! –