¿Por qué no el ejemplo de compilación, también conocido como ¿cómo funciona (co-, contra-, y-) de la varianza de trabajo?

StackOverflow https://stackoverflow.com/questions/663254

Pregunta

Después de esta pregunta, puede alguien 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 puedo usar T).Pero entonces, ¿cómo hace uno para escribir una clase que es covariante en su parámetro de tipo sin tener que recurrir a la creación de la cosa unparametrized?¿Cómo puedo garantizar que sólo puede ser creado con una instancia de T?

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

EDITAR - ahora tengo esta abajo a la siguiente:

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

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

¿Cómo puedo escribir una inmutable Slot clase que es covariante en su tipo?

EDIT 2:Duh!He utilizado var y no val.La siguiente es lo que yo quería:

class Slot[+T] (val some: T) { 
}
¿Fue útil?

Solución

Genéricamente, una covariante el tipo de parámetro es uno que puede variar hacia abajo como la clase es subtipos (como alternativa, puede variar con la tipificación, por lo tanto el "co-" prefijo).Más concretamente:

trait List[+A]

List[Int] es un subtipo de List[AnyVal] porque Int es un subtipo de AnyVal.Esto significa que usted puede proporcionar una instancia de List[Int] cuando un valor de tipo List[AnyVal] es de esperarse.Esto es realmente una forma muy intuitiva para los genéricos para el trabajo, pero resulta que es erróneo (se rompe el tipo de sistema) cuando se utiliza en la presencia de datos mutables.Esta es la razón por la que los genéricos son invariantes en Java.Breve ejemplo de solidez uso de Java arrays (que erróneamente se covariante):

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

Nos acaba de asignar un valor de tipo String para una matriz de tipo Integer[].Por razones que debería ser obvio, esta es una mala noticia.Java del tipo de sistema permite de hecho esta en tiempo de compilación.La JVM se "amablemente" lanzar un ArrayStoreException en tiempo de ejecución.Scala tipo de sistema evita este problema debido a que el parámetro de tipo en la Array la clase es invariante (declaración es [A] en lugar de [+A]).

Tenga en cuenta que hay otro tipo de varianza conocida como contravarianza.Esto es muy importante ya que explica por qué la covarianza puede causar algunos problemas.Contravarianza es, literalmente, el opuesto de la covarianza:los parámetros varían hacia arriba con la tipificación.Es mucho menos común, parcialmente porque es tan contra-intuitivo, aunque tiene una aplicación muy importante:funciones.

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

Aviso de la "-"la varianza de la anotación en el P parámetro de tipo.Esta declaración, como un todo, significa que Function1 es contravariante en P y covariante en R.Por lo tanto, podemos deducir 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) el tipo de T1, que es el opuesto de T2 y T2'.En inglés, esto se puede leer como la siguiente:

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

La razón de esta regla se deja como ejercicio para el lector (sugerencia:pensar en diferentes casos como funciones son subtipos, como mi ejemplo de la matriz de arriba).

Con sus nuevos conocimientos de co - y contravarianza, usted debería ser capaz de ver por qué el siguiente ejemplo no compilar:

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

El problema es que A es covariante, mientras que el cons la función espera que su parámetro de tipo para ser invariantes.Por lo tanto, A es la variación de la dirección equivocada.Curiosamente, podríamos resolver este problema haciendo List contravariante en A, pero, a continuación, el tipo de retorno List[A] no sería válida como la cons la función espera que su tipo de retorno para ser covariante.

Nuestro sólo dos opciones: a) hacer A invariantes, perdiendo el agradable, intuitiva sub-escribir las propiedades de la covarianza, o b) añadir un local de parámetro de tipo para la cons método que define A como un límite inferior:

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

Esto es válido ahora.Usted puede imaginar que A está variando hacia abajo, pero B es capaz de variar al alza con respecto a A desde A es su menor-bound.Con esta declaración de método, podemos tener A ser covariantes y todo funciona.

Aviso que este truco sólo funciona si nos devuelven una instancia de List especializada en el menos específicos B.Si tratas de hacer List mutable, las cosas se rompen hacia abajo desde termina tratando de asignar valores de tipo B a una variable de tipo A, que no está permitido por el compilador.Siempre que tenga capacidad de mutar, usted necesita tener un mutador de algún tipo, que requiere un parámetro de método de un determinado tipo, que (junto con el descriptor de acceso) implica invarianza.La covarianza trabaja con datos inmutables, ya que la única posible, la operación es un descriptor de acceso, el cual puede ser dado una covariante tipo de retorno.

Otros consejos

@Daniel lo ha explicado muy bien. Pero para explicarlo brevemente, si se permitió:

  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 tiempo de ejecución ya que no se pudo convertir un Animal a Dog (¡duh!).

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

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

Si entiendo su comentario correctamente, debe volver a leer el pasaje que comienza en la parte inferior de la página 56 (básicamente, lo que creo que está pidiendo no es de tipo seguro sin verificaciones de tiempo de ejecución, que scala no hace) t hacer, 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 cree que no entiendo su pregunta (una posibilidad distinta), intente agregar más explicación / contexto a la descripción del problema e intentaré nuevamente.

En respuesta a su edición: las ranuras inmutables son una situación completamente diferente ... * sonrisa * Espero que el ejemplo anterior haya ayudado.

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

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

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top