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 +T
y T
en 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 var
y no val
. Lo siguiente es lo que quería:
class Slot[+T] (val some: T) {
}
generics
scala
covariance
contravariance
oxbow_lakes
fuente
fuente
var
es configurable mientrasval
que 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]
porqueInt
es 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
String
a 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"ArrayStoreException
en tiempo de ejecución. El sistema de tipos de Scala previene este problema porque el parámetro de tipo en laArray
clase 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
P
parámetro de tipo. Esta declaración en su conjunto significa queFunction1
es contravarianteP
y 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 paraT2
yT2'
. 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
A
es covariante, mientras que lacons
función espera que su parámetro de tipo sea invariante . Por lo tanto,A
está variando la dirección equivocada. Curiosamente, podríamos resolver este problema haciendoList
contravarianteA
, pero luego el tipo de retornoList[A]
sería inválido ya que lacons
función espera que su tipo de retorno sea covariante .Nuestras únicas dos opciones aquí son: a) hacer
A
invariante, perder las propiedades agradables e intuitivas de subtipo de covarianza, o b) agregar un parámetro de tipo local alcons
método que se defineA
como un límite inferior:Esto ahora es válido. Se puede imaginar que
A
está variando hacia abajo, peroB
es capaz de variar hacia arriba con respecto aA
yaA
es su límite inferior. Con esta declaración de método, podemosA
ser covariantes y todo funciona.Tenga en cuenta que este truco solo funciona si devolvemos una instancia
List
especializada en el tipo menos específicoB
. Si intenta hacerList
mutable, las cosas se descomponen ya que termina tratando de asignar valores de tipoB
a 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.get
luego arrojará un error en tiempo de ejecución ya que no se pudo convertir unAnimal
aDog
(¡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