type parameter generics scala covariance contravariance

parameter - scala generics



¿Por qué no compila el ejemplo, también conocido como ¿cómo funciona(co, contra, y en) la varianza? (4)

@ Daniel lo ha explicado muy bien. Pero para explicarlo en resumen, si fue permitido:

class Slot[+T](var some: T) { def get: T = some } val slot: Slot[Dog] = new Slot[Dog](new Dog) val slot2: Slot[Animal] = slot //because of co-variance slot2.some = new Animal //legal as some is a var slot.get ??

slot.get arrojará un error en el tiempo de ejecución ya que no fue exitoso al convertir un Animal a Dog (duh!).

En general, la mutabilidad no va bien con la covarianza y la contravariancia. Esa es la razón por la cual todas las colecciones de Java son invariables.

A continuación de esta pregunta , ¿alguien puede explicar lo siguiente en Scala:

class Slot[+T] (var some: T) { // DOES NOT COMPILE // "COVARIANT parameter in CONTRAVARIANT position" }

Entiendo la distinción entre +T y T en la declaración de tipo (compila si uso T ). Pero entonces, ¿cómo se escribe realmente una clase que es covariante en su parámetro de tipo sin recurrir a la creación de la cosa sin parametrizar ? ¿Cómo puedo asegurarme de que lo siguiente solo se pueda crear con una instancia de T ?

class Slot[+T] (var some: Object){ def get() = { some.asInstanceOf[T] } }

EDITAR - ahora tengo esto a lo siguiente:

abstract class _Slot[+T, V <: T] (var some: V) { def getT() = { some } }

todo esto está bien, pero ahora tengo dos parámetros de tipo, donde solo quiero uno. Voy a volver a hacer la pregunta así:

¿Cómo puedo escribir una clase de Slot inmóvil que sea covariante en su tipo?

EDICION 2 : ¡Duh! Usé var y no val . Lo siguiente es lo que quería:

class Slot[+T] (val some: T) { }


Debe aplicar un límite inferior en el parámetro. Me está costando recordar la sintaxis, pero creo que se vería así:

class Slot[+T, V <: T](var some: V) { //blah }

El Scala-por-ejemplo es un poco difícil de entender, algunos ejemplos concretos habrían ayudado.


Genéricamente, un parámetro de tipo covariante es uno que se permite que varíe a medida que la clase se subtipo (alternativamente, varía con la subtipificación, de ahí el prefijo "co"). Más concretamente:

trait List[+A]

List[Int] es un subtipo de List[AnyVal] porque Int es un subtipo de AnyVal . Esto significa que puede proporcionar una instancia de List[Int] cuando se espera un valor de tipo List[AnyVal] . Esta es una forma muy intuitiva para que los genéricos funcionen, pero resulta que no es sólida (rompe el sistema de tipos) cuando se usa en presencia de datos mutables. Esta es la razón por la que los genéricos son invariables en Java. Breve ejemplo de falta de solidez utilizando matrices de Java (que son erróneamente covariantes):

Object[] arr = new Integer[1]; arr[0] = "Hello, there!";

Acabamos de asignar un valor de tipo String a una matriz de tipo Integer[] . Por razones que deberían ser obvias, estas son malas noticias. El sistema de tipos de Java realmente permite esto en tiempo de compilación. La JVM arrojará "amablemente" una ArrayStoreException en el tiempo de ejecución. El sistema de tipos de Scala evita este problema porque el parámetro de tipo en la clase Array es invariante (la declaración es [A] lugar de [+A] ).

Tenga en cuenta que existe otro tipo de varianza conocida como contravarianza . Esto es muy importante ya que explica por qué la covarianza puede causar algunos problemas. La contradicción es literalmente lo opuesto a la covarianza: los parámetros varían hacia arriba con la subtipificación. Es mucho menos común en parte porque es muy contraintuitivo, aunque tiene una aplicación muy importante: funciones.

trait Function1[-P, +R] { def apply(p: P): R }

Observe la anotación de varianza " - " en el parámetro de tipo P Esta declaración como un todo significa que la Function1 es contravariante en P y covariante en R Por lo tanto, podemos derivar los siguientes axiomas:

T1'' <: T1 T2 <: T2'' ---------------------------------------- S-Fun Function1[T1, T2] <: Function1[T1'', T2'']

Observe que T1'' debe ser un subtipo (o el mismo tipo) de T1 , mientras que es lo opuesto para T2 y T2'' . En inglés, esto se puede leer como el siguiente:

Una función A es un subtipo de otra función B si el tipo de parámetro de A es un supertipo del tipo de parámetro de B, mientras que el tipo de retorno de A es un subtipo del tipo de retorno de B.

La razón de esta regla se deja como un ejercicio para el lector (sugerencia: piense en diferentes casos a medida que las funciones son subtipificadas, como mi ejemplo de matriz desde arriba).

Con su nuevo conocimiento de co y contravariancia, debería poder ver por qué no se compilará el siguiente ejemplo:

trait List[+A] { def cons(hd: A): List[A] }

El problema es que A es covariante, mientras que la función cons espera que su parámetro de tipo sea invariante . Por lo tanto, A está variando en la dirección incorrecta. Curiosamente, podríamos resolver este problema haciendo que la List contravariante en A , pero luego la List[A] retorno tipo List[A] no sería válida ya que la función de cons espera que su tipo de retorno sea covariante .

Nuestras únicas dos opciones aquí son a) hacer A invariante, perdiendo las propiedades de subtitulación de la covarianza agradables e intuitivas, ob) agregar un parámetro de tipo local al método cons que define A como un límite inferior:

def cons[B >: A](v: B): List[B]

Esto ahora es válido Puedes imaginar que A está variando hacia abajo, pero B puede variar hacia arriba con respecto a A ya que A es su límite inferior. Con esta declaración de método, podemos hacer que A sea ​​covariante y todo funcione.

Tenga en cuenta que este truco solo funciona si devolvemos una instancia de List especializada en el tipo B menos específico. Si intentas hacer que List mutable, las cosas se rompen porque terminas tratando de asignar valores de tipo B a una variable de tipo A , que el compilador no permite. Siempre que tengas mutabilidad, necesitas tener un mutador de algún tipo, que requiere un parámetro de método de cierto tipo, que (junto con el descriptor) implica invarianza. La covarianza funciona con datos inmutables ya que la única operación posible es un descriptor de acceso, que puede recibir un tipo de retorno covariante.


Vea Scala por ejemplo , página 57+ para una discusión completa de esto.

Si entiendo tu comentario correctamente, debes volver a leer el pasaje que comienza en la parte inferior de la página 56 (básicamente, lo que creo que estás pidiendo no es seguro sin controles de tiempo de ejecución, lo que Scala no hace, así que no tienes suerte). Traduciendo su ejemplo para usar su construcción:

val x = new Slot[String]("test") // Make a slot val y: Slot[Any] = x // Ok, ''cause String is a subtype of Any y.set(new Rational(1, 2)) // Works, but now x.get() will blow up

Si siente que no entiendo su pregunta (una posibilidad distinta), intente agregar más explicación / contexto a la descripción del problema y lo intentaré de nuevo.

En respuesta a su edición: las máquinas tragamonedas inmutables son una situación completamente diferente ... * sonríe * Espero que el ejemplo anterior ayude.