jueves, 30 de junio de 2022

Types, Covarianza y Contravarianza


 

Los tipos (types)


empezando por los tipos son una definición del grupo de propiedades que algo tiene por ejemplo si tenemos un String este tiene algunas propiedades como su tamaño y tal vez un método substring que nos permite tomar parte del string etc. mediante el tipo se define esto, cuando usamos tipos en lenguajes tipados lo que buscamos es que sepamos con que estamos interactuando y que operaciones son válidas/qué valores tiene algo.

Estos los vemos por ejemplo cuando declaramos una variable como String, Int, Boolean o cuando creamos una instancia de una interfaz o de una clase.

tipos genéricos (Generic types)


en algunos casos queremos garantizar que algo tenga un tipo pero no nos importa cuál sea este por ejemplo para las lista queremos que las listas solo contengan un tipo de elementos, pero no queremos decir especificamente cual, o queremos crear una funcion que nos permita aplicar validaciones pero no sabemos cuáles van a ser los campos, aquí vienen los Generic types que usualmente se definen en algo como esto.


public static <A> List<A> createListWithTwoElements(A element, A element2){
   return List.of(element,element2);
}

usarlos estos generic types tiene varias ventajas entre ellas:

  • que podemos reusar mas el codigo: por ejemplo el método anterior nos sirve para crear una lista de 2 elementos de varios typo llamándola con diferentes A’s
  • que el compilador nos va a chequear si hacemos algo mal si de repente quiero crear una lista con el primer elemento de un tipo, pero el segundo de otro se va generar un error que queremos que pase para estar seguros de que tenemos en esa lista que crea ese método que tipo

 

Covarianza y ContraVarianza.

Existen 2 tipos de herencia que me parecieron interesantes la covarianza y la contravarianza pero antes de eso vamos definir un árbol de herencias para poder explicar mejor :p

open class LivingThing

open class Animal(val name:String):LivingThing(){
    fun yell(){}
    fun eat(){}
}

data class Sloth(val slothName: String, val isTwoFingered: Boolean):Animal(slothName) {

    fun sleep(){}
}

data class Panda(val pandaName:String):Animal(pandaName){

    fun sleep(){}
}

con esto tenemos más o menos una estructura asi.

 

Donde tenemos a LivingThing(cosa viva) como supertipo de Animal, y Animal como supertipo de Sloth(oso peresozo) y Panda.
Al mismo tiempo Sloth y panda son subtipos de Animal, y Animal es un subtipo de LivinThing.

Covarianza.

en la covarianza decimos que recibimos un tipo o sus subtipos por ejemplo:

  • si recibimos un animal también recibió un Sloth o un Panda en ese parametro.
  • si recibimos un Sloth solo recibimos el Sloth porque este no tiene subtipos.
  • Si recibimos un LIvingThing podemos recibir cualquier cosa en el grafo.


la covarianza es la más natural y sencilla de entender desde mi punto de vista, por que se ve también cuando se castea de un tipo mas especifico a uno mas generico.

en kotlin la covarianza se establece por defecto sin que le pongamos nada simplemente un tipo es covariante por defecto, pero si queremos podemos usar la palabra clave out
<out Animal>.
en el caso de java para obtener covarianza se usa extends
<? extends Animal>
 

Contravarianza.

la contravarianza implica que algo puede recibir un tipo o sus supertipos, ejemplos:

  • si se define contravarianza de Sloth, el parámetro puede tener tipo de un Sloth, un Animal o un LIvingThing
  • si es Animal, puede ser de tipo Animal o LivingThing
  • si es un LivingThing puede solo ser un LivingThing


en kotlin la contravarianza se define así
<in A>
y en java
<? super A>

este es para mi fue mas dificil de entender principalmente por que parece contrario a la intuición, lo pude entender cuando vi un ejemplo de uso.

un árbol de herencias para poder explicar mejor :p
 
fun copy(src: List<out Animal>,dest: MutableList<in Animal>){
    for(a in src){
        dest.add(a)
    }
}


en este podemos ver un método que se encarga de copiar lo que está en input(parámetro src) al output(parámetro dest), en el input recibe una lista de Animales out o covarianza esto nos indica que la lista puede ser una lista de Animales con Sloths y Pandas, por el contrario en la salida tenemos un in de Animal o contravarianza lo que quiere decir que en esta lista vamos a tener Animales o LivingThing

y ahora sí todo empieza a tener sentido recibimos algo específico o su hijo, y generamos ese algo o algo más general

la regla para usar covarianza es para definir productores o get( en el ejemplo anterior estamos sacando de src y por eso lo definimos como covarianza).
y la regla para usar contravarianza es para definir consumidores o put( en el ejemplo anterior estamos guarda o consumiendo en dest).

Esto me lleva a creer más que la herencia es complicada :D .

referencias:


  • https://www.youtube.com/watch?v=yqB8u9higoI&t=2s
  • https://www.youtube.com/watch?v=A7oPKEj1-Fg
  • https://docs.microsoft.com/en-us/archive/blogs/csharpfaq/covariance-and-contravariance-faq
  • https://medium.com/kotlin-thursdays/introduction-to-kotlin-generics-9d18d3719e1d
  • https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

 

No hay comentarios:

Publicar un comentario