arrays - tipos - ¿Por qué las matrices son invariables, pero las listas son covariantes?
que es un array en programacion (4)
Por ejemplo, ¿por qué
val list:List[Any] = List[Int](1,2,3)
trabajo, pero
val arr:Array[Any] = Array[Int](1,2,3)
falla (porque las matrices son invariables). ¿Cuál es el efecto deseado detrás de esta decisión de diseño?
Esto se debe a que las listas son inmutables y las matrices son mutables.
La diferencia es que List
s son inmutables mientras que Array
s son mutables.
Para comprender por qué la mutabilidad determina la varianza, considere crear una versión mutable de List
, llamémosla MutableList
. También haremos uso de algunos tipos de ejemplos: una clase base Animal
y 2 subclases llamados Cat
y Dog
.
trait Animal {
def makeSound: String
}
class Cat extends Animal {
def makeSound = "meow"
def jump = // ...
}
class Dog extends Animal {
def makeSound = "bark"
}
Tenga en cuenta que Cat
tiene un método más ( jump
) que Dog
.
Luego, defina una función que acepte una lista mutable de animales y modifique la lista:
def mindlessFunc(xs: MutableList[Animal]) = {
xs += new Dog()
}
Ahora, cosas horribles sucederán si pasa una lista de gatos a la función:
val cats = MutableList[Cat](cat1, cat2)
val horror = mindlessFunc(cats)
Si utilizáramos un lenguaje de programación descuidado, esto se ignorará durante la compilación. Sin embargo, nuestro mundo no colapsará si solo accedemos a la lista de gatos usando el siguiente código:
cats.foreach(c => c.makeSound)
Pero si hacemos esto:
cats.foreach(c => c.jump)
Se producirá un error de tiempo de ejecución. Con Scala, se evita escribir dicho código porque el compilador se quejará.
La respuesta normal es que la mutabilidad combinada con la covarianza rompería la seguridad del tipo. Para las colecciones, esto puede tomarse como una verdad fundamental. Pero la teoría en realidad se aplica a cualquier tipo genérico, no solo colecciones como List
y Array
, y no tenemos que intentar razonar acerca de la mutabilidad en absoluto.
La respuesta real tiene que ver con la forma en que los tipos de funciones interactúan con la subtipificación. La historia corta es que si un parámetro de tipo se usa como un tipo de retorno, es covariante. Por otro lado, si un parámetro de tipo se utiliza como un tipo de argumento, es contravariante. Si se usa tanto como un tipo de devolución como como un tipo de argumento, es invariante.
Veamos la documentación de Array[T]
. Los dos métodos obvios para mirar son los de búsqueda y actualización:
def apply(i: Int): T
def update(i: Int, x: T): Unit
En el primer método, T
es un tipo de retorno, mientras que en el segundo, T
es un tipo de argumento. Las reglas de la varianza dictan que T
debe por lo tanto ser invariante.
Podemos comparar la documentación de la List[A]
para ver por qué es covariante. Confusamente, encontraríamos estos métodos, que son análogos a los métodos para Array[T]
:
def apply(n: Int): A
def ::(x: A): List[A]
Como A
se usa como un tipo de retorno y como un tipo de argumento, esperaríamos que A
fuera invariante al igual que T
es para Array[T]
. Sin embargo, a diferencia de Array[T]
, la documentación nos está mintiendo sobre el tipo de ::
. La mentira es lo suficientemente buena para la mayoría de las llamadas a este método, pero no es lo suficientemente buena como para decidir la varianza de A
Si ampliamos la documentación de este método y hacemos clic en "Firma completa", se nos muestra la verdad:
def ::[B >: A](x: B): List[B]
Entonces, A
realmente no aparece como un tipo de argumento. En cambio, B
(que puede ser cualquier supertipo de A
) es el tipo de argumento. Esto no impone ninguna restricción sobre A
, por lo que realmente puede ser covariante. Cualquier método en la List[A]
que tenga A
como un tipo de argumento es una mentira similar (podemos decirlo porque estos métodos están marcados como [use case]
).
Porque de lo contrario rompería la seguridad de tipo. Si no, serás capaz de hacer algo como esto:
val arr:Array[Int] = Array[Int](1,2,3)
val arr2:Array[Any] = arr
arr2(0) = 2.54
y el compilador no puede atraparlo.
Por otro lado, las listas son inmutables, por lo que no puede agregar algo que no sea Int