miércoles, 8 de mayo de 2024

Que es la Covarianza, Contravarianza y Invarianza?


Ya sé que estos son temas super basicos pero siempre que hablan de eso me los confundo. Por eso voy a hacer este post para ver si porfin puedo fijar las ideas. 

La varianza y la covarianza son conceptos importantes en el contexto de los tipos genérico. Estos conceptos se refieren a cómo se relacionan los tipos genéricos cuando se consideran subtipos y super tipos. Veamos una explicación de cada uno:

Covarianza: La covarianza se refiere a la relación entre tipos genéricos donde la relación de subtipos se mantiene en la misma dirección que la relación de subtipos de los parámetros de tipo. En otras palabras, si tenemos un tipo T y otro tipo U donde U es un subtipo de T, entonces podemos decir que List<U> es un subtipo de List<T>. Esto significa que podemos asignar una lista de subtipos a una lista de supertipos sin necesidad de conversión explícita. La covarianza se utiliza típicamente en situaciones donde solo se lee de una estructura de datos, como en secuencias o enumeraciones.

Veamos un ejemplo en C#: 

using System;

using System.Collections.Generic;


class Program

{

    static void Main()

    {

        // Covarianza en IEnumerable<T>

        IEnumerable<string> strings = new List<string> { "hello", "world" };

        PrintItems(strings);

    }


    static void PrintItems(IEnumerable<object> items)

    {

        foreach (var item in items)

        {

            Console.WriteLine(item);

        }

    }

}

Veamos un ejemplo en Java: 

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Covarianza en List<? extends Number>
        List<Integer> integers = new ArrayList<>();
        integers.add(1);
        integers.add(2);
        printNumbers(integers);
    }

    static void printNumbers(List<? extends Number> numbers) {
        for (Number number : numbers) {
            System.out.println(number);
        }
    }
}

Y en scala: 

class Main {
  def main(args: Array[String]): Unit = {
    // Covarianza en List[+A]
    val strings: List[String] = List("hello", "world")
    printItems(strings)
  }

  def printItems(items: List[Any]): Unit = {
    items.foreach(println)
  }
}

Contravarianza: La contravarianza es el opuesto de la covarianza. Se refiere a la relación entre tipos genéricos donde la relación de subtipos se invierte en comparación con la relación de subtipos de los parámetros de tipo. En otras palabras, si tenemos un tipo T y otro tipo U donde U es un subtipo de T, entonces podemos decir que Action<T> es un subtipo de Action<U>. Esto significa que podemos asignar una función que acepta supertipos a una variable de función que acepta subtipos. La contravarianza se utiliza típicamente en situaciones donde solo se escribe en una estructura de datos, como en consumidores de datos o comparadores.

Veamos un ejemplo en Java: 


import java.util.function.Consumer;


public class Main {

    public static void main(String[] args) {

        // Contravarianza en Consumer<? super String>

        Consumer<Object> printString = Main::printString;

        printString.accept("hello");

    }


    static void printString(Object s) {

        System.out.println(s);

    }

}


Y en Scala: 

class Main {
  def main(args: Array[String]): Unit = {
    // Contravarianza en Function1[-T, +R]
    val printString: Any => Unit = Main.printString
    printString("hello")
  }
}

object Main {
  def printString(s: String): Unit = {
    println(s)
  }
}

Invarianza: La invarianza es el tercer escenario, donde no hay una relación de subtipos entre tipos genéricos. En otras palabras, List<T> no es ni un subtipo ni un supertipo de List<U> si T y U son tipos diferentes, incluso si U es un subtipo de T o viceversa. En este caso, necesitamos una conversión explícita para asignar entre los dos tipos.

En C#, la covarianza y la contravarianza se expresan a través de modificadores como out y in en declaraciones de tipo genérico. En Java, estos conceptos se implementan a través de la notación <? extends T> para la covarianza y <? super T> para la contravarianza en tipos genéricos. Es importante entender estos conceptos para escribir código genérico seguro y comprensible.

No recuerdo haber utilizado la contravarianza pero no se me hace algo tan común. Y ustedes, han utilizado estas técnicas? 


No hay comentarios.:

Publicar un comentario