Siguiendo con 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 +Ty Ten la declaración de tipo (se compila si la uso T). Pero entonces, ¿cómo se escribe realmente una clase que es covariante en su parámetro de tipo sin recurrir a crear la cosa sin parametrizar ? ¿Cómo puedo asegurarme de que lo siguiente solo se puede crear con una instancia de T?
class Slot[+T] (var some: Object){    
  def get() = { some.asInstanceOf[T] }
}EDITAR : ahora esto se reduce 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. Volveré a hacer la pregunta así:
¿Cómo puedo escribir una clase inmutable Slot que sea covariante en su tipo?
EDITAR 2 : ¡Duh! Yo solía vary no val. Lo siguiente es lo que quería:
class Slot[+T] (val some: T) { 
}
                    
                        generics
                                scala
                                covariance
                                contravariance
                                
                    
                    
                        oxbow_lakes
fuente
                
                fuente

vares configurable mientrasvalque no lo es. Es la misma razón por la cual las colecciones inmutables de scala son covariantes, pero las mutables no lo son.Respuestas:
Genéricamente, un parámetro de tipo covariante es aquel que puede variar a medida que la clase se subtipea (alternativamente, varía según el subtipo, de ahí el prefijo "co-"). Más concretamente:
List[Int]es un subtipo deList[AnyVal]porqueIntes un subtipo deAnyVal. Esto significa que puede proporcionar una instancia deList[Int]cuándoList[AnyVal]se espera un valor de tipo . Esta es realmente una forma muy intuitiva para que funcionen los genéricos, pero resulta que no es sólida (rompe el sistema de tipos) cuando se usa en presencia de datos mutables. Es por eso que los genéricos son invariantes en Java. Breve ejemplo de falta de solidez utilizando matrices Java (que son erróneamente covariantes):Acabamos de asignar un valor de tipo
Stringa una matriz de tipoInteger[]. 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 lanzará un "útilmente"ArrayStoreExceptionen tiempo de ejecución. El sistema de tipos de Scala previene este problema porque el parámetro de tipo en laArrayclase es invariable (la declaración es[A]más que[+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. La contravarianza es literalmente lo opuesto a la covarianza: los parámetros varían hacia arriba con el subtipo. Es mucho menos común en parte porque es muy intuitivo, aunque tiene una aplicación muy importante: las funciones.
Observe la anotación de varianza " - " en el
Pparámetro de tipo. Esta declaración en su conjunto significa queFunction1es contravariantePy covariante enR. Por lo tanto, podemos derivar los siguientes axiomas:Tenga en cuenta que
T1'debe ser un subtipo (o el mismo tipo) deT1, mientras que es lo contrario paraT2yT2'. En inglés, esto se puede leer de la siguiente manera:La razón de esta regla se deja como un ejercicio para el lector (pista: piense en diferentes casos a medida que las funciones están subtipadas, como mi ejemplo de matriz de arriba).
Con su nuevo conocimiento de co y contravarianza, debería poder ver por qué el siguiente ejemplo no se compilará:
El problema es que
Aes covariante, mientras que laconsfunción espera que su parámetro de tipo sea invariante . Por lo tanto,Aestá variando la dirección equivocada. Curiosamente, podríamos resolver este problema haciendoListcontravarianteA, pero luego el tipo de retornoList[A]sería inválido ya que laconsfunción espera que su tipo de retorno sea covariante .Nuestras únicas dos opciones aquí son: a) hacer
Ainvariante, perder las propiedades agradables e intuitivas de subtipo de covarianza, o b) agregar un parámetro de tipo local alconsmétodo que se defineAcomo un límite inferior:Esto ahora es válido. Se puede imaginar que
Aestá variando hacia abajo, peroBes capaz de variar hacia arriba con respecto aAyaAes su límite inferior. Con esta declaración de método, podemosAser covariantes y todo funciona.Tenga en cuenta que este truco solo funciona si devolvemos una instancia
Listespecializada en el tipo menos específicoB. Si intenta hacerListmutable, las cosas se descomponen ya que termina tratando de asignar valores de tipoBa una variable de tipoA, que el compilador no permite. Siempre que tenga mutabilidad, debe tener un mutador de algún tipo, que requiere un parámetro de método de cierto tipo, que (junto con el descriptor de acceso) implica invariancia. La covarianza funciona con datos inmutables ya que la única operación posible es un descriptor de acceso, al que se le puede dar un tipo de retorno covariante.fuente
trait Animal,trait Cow extends Animal,def iNeedACowHerder(herder: Cow => Unit, c: Cow) = herder(c)ydef iNeedAnAnimalHerder(herder: Animal => Unit, a: Animal) = herder(a). Entonces,iNeedACowHerder({ a: Animal => println("I can herd any animal, including cows") }, new Cow {})está bien, ya que nuestro pastor de animales puede criar vacas, peroiNeedAnAnimalHerder({ c: Cow => println("I can herd only cows, not any animal") }, new Animal {})da un error de compilación, ya que nuestro pastor de vacas no puede criar a todos los animales.@Daniel lo ha explicado muy bien. Pero para explicarlo brevemente, si se permitiera:
slot.getluego arrojará un error en tiempo de ejecución ya que no se pudo convertir unAnimalaDog(¡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.
fuente
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, así que no tienes suerte). Traduciendo su ejemplo para usar su construcción:
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 tragamonedas inmutables son una situación completamente diferente ... * sonrisa * Espero que el ejemplo anterior haya ayudado.
fuente
Debe aplicar un límite inferior en el parámetro. Me cuesta recordar la sintaxis, pero creo que se vería así:
El Scala-by-example es un poco difícil de entender, algunos ejemplos concretos habrían ayudado.
fuente