2012-08-11 11 views
8

Se supone que el método de las clases de caso copy() hace una copia idéntica de la instancia, además de reemplazar cualquier campo por nombre. Esto parece fallar cuando la clase case tiene parámetros de tipo con manifiestos. La copia pierde todo el conocimiento de los tipos de sus parámetros.Scala: Cómo hacer que la copia de la clase de caso guarde la información del manifiesto

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" 

¿Por qué Foo.copy perder el manifiesto de los parámetros y cómo puedo hacer que retenerlo?

Respuesta

15

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 
+0

¡Buen trabajo, amigo! –

1

Hay dos problemas que ha identificado. El primer problema es el problema de borrado de tipo dentro de Bar, donde Bar no conoce el tipo de manifiesto de Foo. Usaría personalmente la solución localCopy que sugirió.

El segundo problema es que otro implícito se está inyectando secretamente en copy. Ese problema se resuelve al pasar explícitamente el valor nuevamente al copy. Por ejemplo:

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 

Vemos la copia ha mantenido el manifiesto Int pero el tipo de fooCopy y res2 es Manifest[Any] debido al borrado.

Como necesitaba acceso a la evidencia implícita para hacer el copy tuve que usar la sintaxis explícita implicit (hah) en lugar de la sintaxis del contexto. Pero el uso de la sintaxis explícita causó errores:

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? ¿Cómo es que la sintaxis del límite de contexto funciona y el implicit explícito no funciona? Busqué y encontré una solución al problema: la anotación @uncheckedVariance.

ACTUALIZACIÓN

Cavé alrededor de un poco más y encontró que en Scala 2.10 clases de casos han sido cambiados para solamente copiar los campos de la primera lista de parámetros en copy().

Martin dice: case class ness solo se concede en el primer argumento lista el resto no debe copiarse.

Consulte los detalles de este cambio en https://issues.scala-lang.org/browse/SI-5009.

+0

Estaba intentando hacer esto pero no sabía sobre '@ uncheckedVariance'. ¿Puede eso llevar a algunas asignaciones poco acertadas, o es la necesidad de ello simplemente un artefacto del hecho de que no hay 'Manifest' covariantes y contravariantes por separado en Scala? – drhagen

+0

Creo que es solo un artefacto. Me frustró que la sintaxis del límite de contexto funcionó, pero la sintaxis implícita explícita no funcionaba, así que busqué y encontré '@ uncheckedVariance'. Supongo que la sintaxis del contexto está básicamente haciendo '@ uncheckedVariance' detrás de escena. – sourcedelica

+0

Se agregó información sobre '@ uncheckedVariance' a la respuesta. – sourcedelica

Cuestiones relacionadas