2012-03-08 7 views
31

Estoy intentando utilizar un parámetro de tipo covariante dentro de un rasgo de construir un caso de clase de este modo:¿Por qué el parámetro está en una posición contravariante?

trait MyTrait[+T] { 
    private case class MyClass(c: T) 
} 

compilador dice:

error: covariant type T occurs in contravariant position in type T of value c 

Luego probé el siguiente pero también dejase 't trabajo:

trait MyTrait[+T] { 
    private case class MyClass[U <: T](c: U) 
} 

el error en esta ocasión es:

error: covariant type T occurs in contravariant position in type >: Nothing <: T of type U 

¿Podría alguien explicar por qué la T está en una posición covariante aquí y sugerir una solución para este problema? ¡Thx!

+0

¿Podría explicar qué es lo que realmente quiere hacer? ¿Por qué quieres T covariante y no invariante? –

Respuesta

61

Esta es una característica fundamental de la programación orientada a objetos que no recibe tanta atención como se merece.

Supongamos que tiene una colección C[+T]. Lo que +T significa es que si U <: T, entonces C[U] <: C[T]. Lo suficientemente justo. Pero, ¿qué significa ser una subclase? Significa que cada método debería funcionar que funcionó en la clase original. Entonces, supongamos que tiene un método m(t: T). Esto dice que puede tomar cualquier t y hacer algo con él. ¡Pero C[U] solo puede hacer cosas con U, que podría no ser todo de T! De modo que ha contradicho inmediatamente su afirmación de que C[U] es una subclase de C[T]. Es no. Hay cosas que puede hacer con un C[T] que no puede hacer con un C[U].

Ahora, ¿cómo se soluciona esto?

Una opción es hacer que la clase sea invariable (descarta el +). Otra opción es que si toma un parámetro de método, para permitir cualquier superclase también: m[S >: T](s: S). Ahora si T cambia a U, no es gran cosa: una superclase de T también es una superclase de U, y el método funcionará. (Sin embargo, luego debe cambiar su método para poder manejar tales cosas).

Con una clase de caso, es incluso más difícil hacerlo bien a menos que lo haga invariante. Recomiendo hacer eso, y presionar los genéricos y la varianza en otro lugar. Pero necesitaría ver más detalles para estar seguro de que esto funcionaría para su caso de uso.

+0

gracias por su respuesta. Sin embargo, su solución no funciona para mí en este caso. Dejar caer la covarianza y hacer que el rasgo invariante funcione, pero no es lo que quiero aquí. Permitir que el método (o en mi caso, la clase de caso) tome un supertipo tampoco es satisfactorio. Tengo curiosidad por saber por qué las cosas son más difíciles de resolver para las clases de casos. Tenga en cuenta que el mismo código sin la palabra clave _case_ funciona bien. – lapislazuli

+2

@lapislazuli - Porque las clases de casos incluyen un método complementario que las crea (tomando 'T' como argumento), por lo que debe obedecer las restricciones de métodos anteriores. Si no incluye 'case', entonces la clase no implica un método en la interfaz que tome' T's. –

+0

gracias rex, ¡ahora tiene sentido para mí! – lapislazuli

13

Casi allí. Aquí:

scala> trait MyTrait[+T] { 
    | private case class MyClass[U >: T](c: U) 
    | } 
defined trait MyTrait 

Lo que significa MyClass[Any] es válida para todos T. Esa es la raíz de por qué no se puede usar T en esa posición, pero demostrarlo requiere más código de lo que estoy de humor en este momento. :-)

Cuestiones relacionadas