Translate

viernes, 25 de octubre de 2024

copy_if de C++


La función std::copy_if de la biblioteca estándar de C++ copia los elementos de un rango (input range) a otro rango (output range), siempre que los elementos cumplan con una condición dada. Esta condición se define mediante un predicado que debe evaluarse a `true` para que el elemento sea copiado.

Veamos ejemplos de uso:


#include <iostream>

#include <vector>

#include <algorithm>


int main() {

    std::vector<int> numeros = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    std::vector<int> pares;


    // Copiar los números pares a 'pares'

    std::copy_if(numeros.begin(), numeros.end(), std::back_inserter(pares), [](int n) {

        return n % 2 == 0;

    });


    // Imprimir los números pares

    std::cout << "Números pares: ";

    for (int n : pares) {

        std::cout << n << " ";

    }

    std::cout << std::endl;


    return 0;

}


Salida:

Números pares: 2 4 6 8 10


Veamos otro ejemplo: 


#include <iostream>

#include <vector>

#include <string>

#include <algorithm>


int main() {

    std::vector<std::string> palabras = {"casa", "carro", "perro", "gato", "ciudad"};

    std::vector<std::string> contieneLetraC;


    // Copiar las palabras que contienen la letra 'c'

    std::copy_if(palabras.begin(), palabras.end(), std::back_inserter(contieneLetraC), [](const std::string& palabra) {

        return palabra.find('c') != std::string::npos;

    });


    // Imprimir las palabras que contienen la letra 'c'

    std::cout << "Palabras con 'c': ";

    for (const auto& palabra : contieneLetraC) {

        std::cout << palabra << " ";

    }

    std::cout << std::endl;

    return 0;

}


Salida: 

Palabras con 'c': casa carro ciudad


Filtrar números negativos


#include <iostream>

#include <vector>

#include <algorithm>


int main() {

    std::vector<int> numeros = {5, -3, 7, -1, 9, -10, 0};

    std::vector<int> negativos;


    // Copiar los números negativos

    std::copy_if(numeros.begin(), numeros.end(), std::back_inserter(negativos), [](int n) {

        return n < 0;

    });


    // Imprimir los números negativos

    std::cout << "Números negativos: ";

    for (int n : negativos) {

        std::cout << n << " ";

    }

    std::cout << std::endl;


    return 0;

}

Salida:

Números negativos: -3 -1 -10


Como linea general para utilizar copy_if podemos decir: 

  • std::copy_if recorre un rango de entrada y copia los elementos que satisfacen una condición dada al rango de salida.
  • Necesitas un predicado (una función o lambda que retorne `true` o `false`) que defina qué elementos deben copiarse.
  • Usa un iterator de salida, como `std::back_inserter`, para gestionar la inserción en el contenedor de destino sin necesidad de redimensionarlo manualmente.


miércoles, 23 de octubre de 2024

Tipos Unión en Python


Python introdujo una forma más sencilla de manejar los tipos unión a partir de la versión 3.10, utilizando el operador `|`.

Antes de Python 3.10, las uniones de tipos se representaban usando `Union` del módulo `typing`. Por ejemplo:


from typing import Union


def procesar_valor(valor: Union[int, float]) -> None:

    print(valor)



Esto indica que `valor` puede ser un `int` o un `float`. Sin embargo, esta sintaxis fue simplificada en Python 3.10 con el uso del operador `|`, lo que mejora la legibilidad:


def procesar_valor(valor: int | float) -> None:

    print(valor)


Ambas formas son equivalentes, pero la nueva es más concisa y fácil de leer.

 Ejemplo práctico:


def manejar_respuesta(respuesta: str | None) -> str:

    if respuesta is None:

        return "No hay respuesta"

    return respuesta


TypeScript, un superconjunto de JavaScript, también soporta tipos unión, permitiendo que una variable pueda tener más de un tipo. Al igual que Python, utiliza un símbolo para definir uniones, en este caso también el `|`:


function procesarValor(valor: number | string): void {

    console.log(valor);

}


TypeScript es un lenguaje estáticamente tipado, lo que significa que el compilador verifica los tipos en tiempo de compilación. Si una operación no es válida para uno de los tipos en la unión, el compilador lanzará un error. Python, por otro lado, realiza la verificación de tipos solo cuando se ejecuta (en tiempo de ejecución).

Intersecciones: TypeScript tiene algo llamado tipos intersección (`&`), donde un valor debe cumplir con todas las condiciones de varios tipos al mismo tiempo, lo que no existe en Python.


function combinar(valores: string | number[]): number {

    if (typeof valores === 'string') {

        return valores.length;

    } else {

        return valores.reduce((a, b) => a + b, 0);

    }

}


En este caso, `valores` puede ser una cadena o un arreglo de números, y se maneja cada tipo de forma separada.

El concepto de tipos unión es fundamental para manejar situaciones en las que un valor puede ser de varios tipos. En Python, con la introducción del operador `|` en la versión 3.10, el manejo de uniones se ha vuelto más simple y elegante. 

Los tipos unión son una excelente herramienta en lenguajes con tipado opcional o estático, permitiendo crear código más robusto y manejable cuando se espera que los datos puedan variar en su forma.

martes, 22 de octubre de 2024

Borgo, lenguaje de programación que compila a Go.


Borgo es un lenguaje de programación relativamente nuevo que está diseñado para ser versátil y eficiente en varios entornos de desarrollo. Aunque aún está en sus primeras etapas, promete ofrecer un equilibrio entre alto rendimiento y facilidad de uso, haciendo énfasis en la simplicidad y la expresividad del código.

  1. Simplicidad y Legibilidad: Uno de los pilares de Borgo es la simplicidad en su sintaxis. Se enfoca en evitar la verbosidad innecesaria, permitiendo que los desarrolladores escriban código más limpio y comprensible.
  2. Eficiencia y Rendimiento: Borgo está optimizado para ejecutar código de manera eficiente, lo que lo hace ideal para aplicaciones de alto rendimiento. Su compilador se enfoca en generar binarios pequeños y rápidos.
  3. Soporte para Programación Funcional y Orientada a Objetos: Al igual que lenguajes como Scala y Kotlin, Borgo combina paradigmas funcionales y orientados a objetos, lo que permite a los desarrolladores elegir el enfoque más adecuado para su proyecto.
  4. Concurrencia Nativa: Al ser un lenguaje moderno, Borgo incluye soporte para concurrencia y paralelismo de manera nativa, facilitando la creación de aplicaciones altamente escalables sin tener que recurrir a bibliotecas externas.
  5. Ecosistema Modular: Borgo apuesta por un ecosistema modular, donde los desarrolladores pueden añadir funcionalidad mediante paquetes externos, similar a lo que ofrecen lenguajes como Python con `pip` o Node.js con `npm`.

Relindas las características pero veamos un poco de código: 


use fmt

enum NetworkState<T> {

    Loading,

    Failed(int),

    Success(T),

}


struct Response {

    title: string,

    duration: int,

}


fn main() {

    let res = Response {

        title: "Hello world",

        duration: 0,

    }


    let state = NetworkState.Success(res)


    let msg = match state {

        NetworkState.Loading => "still loading",

        NetworkState.Failed(code) => fmt.Sprintf("Got error code: %d", code),

        NetworkState.Success(res) => res.title,

    }


    fmt.Println(msg)

}


Como ven, es como que go y rust hubieran tenido un hijo... 

Borgo es un lenguaje emergente con mucho potencial, diseñado para combinar simplicidad con eficiencia. Aunque aún no es ampliamente adoptado, sus características prometen hacer que valga la pena seguirlo de cerca, especialmente para aquellos desarrolladores que buscan una alternativa moderna a lenguajes tradicionales. 

Dejo link: https://borgo-lang.github.io/

viernes, 18 de octubre de 2024

Respaldos externos de Gleam


import gleam/io


@external(erlang, "lists", "reverse")

pub fn reverse_list(items: List(e)) -> List(e) {

  tail_recursive_reverse(items, [])

}


fn tail_recursive_reverse(items: List(e), reversed: List(e)) -> List(e) {

  case items {

    [] -> reversed

    [first, ..rest] -> tail_recursive_reverse(rest, [first, ..reversed])

  }

}


pub fn main() {

  io.debug(reverse_list([1, 2, 3, 4, 5]))

  io.debug(reverse_list(["a", "b", "c", "d", "e"]))

}

Y el resultado es: 

[5, 4, 3, 2, 1]
["e", "d", "c", "b", "a"]

Es posible que una función tenga tanto una implementación de Gleam como una implementación externa. Si existe una implementación externa para el objetivo compilado actualmente, se utilizará; de lo contrario, se utilizará la implementación de Gleam.

Esto puede resultar útil si tiene una función que se puede implementar en Gleam, pero hay una implementación optimizada que se puede utilizar para un objetivo. Por ejemplo, la máquina virtual Erlang tiene una función de inversión de lista incorporada que se implementa en código nativo. El código aquí utiliza esta implementación cuando se ejecuta en Erlang, ya que está disponible en ese momento.

miércoles, 16 de octubre de 2024

Un ejemplo sencillo de STL en c++


Hagamos un ejemplo sencillo en C++ utilizando STL, queremos calcular el promedio general de un curso. Es decir, el promedio del promedio de todos los alumnos.

Entonces tenemos a la clase Alumno: 

class Alumno

{

private:

    char * nombre;

    std::vector<int> notas;

public:

    Alumno(char * nombre);

    char *getNombre() const;

    void setNombre(char *newNombre);

    double promedio();

    void addNota(int nota);

};


Y Curso: 


class Curso

{

private:

    char * nombre;

    std::vector<Alumno> alumnos;

public:

    Curso(char * nombre);

    char *getNombre() const;

    void setNombre(char *newNombre);

    void addAlumno(Alumno a);

    double promedioGeneral();

};


No copio todo el cpp porque me va quedar un post muy largo, pero es fácil de implementar. Ahora bien, primero calculamos el promedio de los alumnos: 


double Alumno::promedio()
{
    int sum = std::reduce(this->notas.begin(),
                          this->notas.end(),
                          0);
    return sum / this->notas.size();
}


Y luego el promedio de los alumnos, para esto pasamos el vector de alumnos a un vector de promedios y luego calculamos el promedio: 

double Curso::promedioGeneral()
{
    std::vector<double> promedios;
    std::transform(this->alumnos.begin(),
                   this->alumnos.end(),
                   std::back_inserter(promedios),
            [](Alumno elem) {
                return elem.promedio(); 
            });
    double acu = std::reduce(promedios.begin(),
                             promedios.end(),
                                   0);
    return acu / promedios.size();
}

Ya sé que podria solo calcular el promedio con reduce pero hago el transform, solo para hacer más completo el ejemplo. 

Y para probarlo podemos hacer : 

using namespace std;

int main()
{
    Alumno unAlumno("Juan");
    unAlumno.addNota(65);
    unAlumno.addNota(75);
    unAlumno.addNota(85);

    Alumno otroAlumno("Pedro");
    otroAlumno.addNota(64);
    otroAlumno.addNota(56);
    otroAlumno.addNota(85);

    Curso cpp("curso c++");
    cpp.addAlumno(unAlumno);
    cpp.addAlumno(otroAlumno);

    cout << "Promedio general :" << cpp.promedioGeneral() << endl;

    return 0;
}

Y listo!! 

No hay que olvidarse importar vector y numeric:

#include <vector>
#include <numeric>

lunes, 14 de octubre de 2024

Implementaciones externas para varios destinos


import gleam/io


pub type DateTime


@external(erlang, "calendar", "local_time")

@external(javascript, "./my_package_ffi.mjs", "now")

pub fn now() -> DateTime


pub fn main() {

  io.debug(now())

}


Se pueden especificar varias implementaciones externas para la misma función, lo que permite que la función funcione tanto en Erlang como en JavaScript.

Si una función no tiene una implementación para el destino compilado actualmente, el compilador devolverá un error.

Se debe implementar funciones para todos los destinos, pero esto no siempre es posible debido a incompatibilidades en cómo funcionan la E/S y la concurrencia en Erlang y JavaScript. Con Erlang, la E/S simultánea se maneja de forma transparente por el entorno de ejecución, mientras que en JavaScript la E/S simultánea requiere el uso de promesas o devoluciones de llamadas. Si su código utiliza el estilo Erlang, normalmente no es posible implementarlo en JavaScript, mientras que si se utilizan devoluciones de llamadas, no será compatible con la mayoría del código Gleam y Erlang, ya que obliga a cualquier código que llame a la función a utilizar también devoluciones de llamadas.

Las bibliotecas que utilizan E/S simultáneas normalmente tendrán que decidir si admiten Erlang o JavaScript y documentar esto en su README.

viernes, 11 de octubre de 2024

Función map, reduce y filter en Typescript


La función map te permite transformar cada elemento de un array aplicando una función sobre ellos. El resultado es un nuevo array con los valores transformados.


const numeros = [1, 2, 3, 4, 5];

const duplicados = numeros.map((num) => num * 2);

console.log(duplicados); // Salida: [2, 4, 6, 8, 10]


filter se utiliza para eliminar elementos de un array que no cumplan una condición específica. El resultado es un nuevo array solo con los elementos que pasen esa condición.


const numeros = [1, 2, 3, 4, 5];

const pares = numeros.filter((num) => num % 2 === 0);

console.log(pares); // Salida: [2, 4]


reduce es una función poderosa que permite reducir un array a un único valor aplicando una función acumuladora a sus elementos.


const numeros = [1, 2, 3, 4, 5];

const suma = numeros.reduce((acumulador, num) => acumulador + num, 0);

console.log(suma); // Salida: 15


Este ejemplo reduce el array sumando todos los números, resultando en 15. El 0 es el valor inicial del acumulador.

Usar técnicas de programación funcional como map, filter y reduce mejora la legibilidad del código al evitar bucles explícitos y efectos secundarios. Este estilo declarativo hace que sea más fácil razonar sobre el comportamiento de tu aplicación, especialmente cuando trabajas con grandes cantidades de datos o flujos.



miércoles, 9 de octubre de 2024

Externals en Gleam


import gleam/io


// A type with no Gleam constructors

pub type DateTime


// An external function that creates an instance of the type

@external(javascript, "./my_package_ffi.mjs", "now")

pub fn now() -> DateTime


// The `now` function in `./my_package_ffi.mjs` looks like this:

// export function now() {

//   return new Date();

// }


pub fn main() {

  io.debug(now())

}


El resultado es : 

//js(Date("2024-10-05T17:29:39.958Z"))


A veces, en nuestros proyectos, queremos usar código escrito en otros lenguajes, más comúnmente Erlang y JavaScript, dependiendo del entorno de ejecución que se esté usando. Las funciones externas y los tipos externos de Gleam nos permiten importar y usar este código que no es de Gleam.

Un tipo externo es aquel que no tiene constructores. Gleam no sabe qué forma tiene ni cómo crear uno, solo sabe que existe.

Una función externa es aquella que tiene el atributo @external, que indica al compilador que use la función del módulo especificado como implementación, en lugar del código de Gleam.

El compilador no puede determinar los tipos de funciones escritas en otros lenguajes, por lo que cuando se proporciona el atributo externo, se deben proporcionar anotaciones de tipo. Gleam confía en que el tipo proporcionado sea correcto, por lo que una anotación de tipo inexacta puede generar un comportamiento inesperado y fallas en el entorno de ejecución. ¡Ten cuidado!

Las funciones externas son útiles, pero se deben usar con moderación. Es preferible escribir código de Gleam cuando sea posible.

lunes, 7 de octubre de 2024

Evolución de Pattern Matching en C#


El Pattern Matching es una poderosa característica de C# que permite realizar operaciones basadas en coincidencias de patrones. A medida que han pasado las versiones, esta funcionalidad se ha enriquecido significativamente. A continuación, se explica cómo ha evolucionado:

Type Pattern (C# 7.0)

Permite verificar y hacer un cast de un tipo directamente.


if (obj is int i)

{

    Console.WriteLine(i); // Desde C# 7.0

}


Switch con Pattern Matching (C# 7.0)

El switch puede realizar comparaciones de tipos.


switch (obj)

{

    case int i:

        Console.WriteLine(i);  // Desde C# 7.0

        break;

}


Property Pattern (C# 8.0)

Verifica propiedades de un objeto en un switch.


var person = new Person("John", 30);

var result = person switch

{

    { Name: "John", Age: 30 } => "Matched!", // Desde C# 8.0

    _ => "No Match"

};


Positional Pattern (C# 8.0)

Permite trabajar con tipos que tienen métodos `Deconstruct`.


public record Point(int X, int Y);


var point = new Point(1, 2);

string description = point switch

{

    (1, 2) => "Point at (1,2)",  // Desde C# 8.0

    _ => "Other point"

};


Logical Patterns (and, or, not) (C# 9.0)

Se pueden combinar patrones usando operadores lógicos.


int x = 5;

string result = x switch

{

    > 0 and < 10 => "Between 1 and 9",   // Desde C# 9.0

    _ => "Other"

};


Relational Pattern (C# 9.0)

Se usa para comparar valores numéricos directamente en los patrones.


int x = 20;

string result = x switch

{

    > 10 => "Greater than 10",  // Desde C# 9.0

    _ => "10 or less"

};



List Patterns (C# 11.0)

Trabaja con colecciones de una manera más directa.


int[] numbers = { 1, 2, 3 };

var result = numbers switch

{

    [1, 2, ..] => "Starts with 1, 2",  // Desde C# 11.0

    _ => "Other"

};


 Slice Patterns (C# 11.0)

Captura sublistas utilizando `..`.


int[] numbers = { 1, 2, 3, 4 };

var result = numbers switch

{

    [_, _, .. int[] rest] => $"Rest: {string.Join(", ", rest)}",  // Desde C# 11.0

    _ => "No Match"

};


Y falto hablar de las Guards. Un guard es una condición adicional que puedes usar dentro de un patrón de coincidencia para hacer una verificación más específica. Se utiliza con la palabra clave when. Veamos un ejemplo:


int number = 5;


string result = number switch

{

    int n when n > 0 => "Positive number",

    int n when n < 0 => "Negative number",

    _ => "Zero"

};


Console.WriteLine(result);  // Output: Positive number


El Pattern Matching ha evolucionado desde su introducción en C# 7.0, permitiendo escribir código más expresivo, con patrones que facilitan el trabajo con tipos, propiedades, listas y operadores lógicos. Las versiones más recientes, como C# 11.0, añaden aún más flexibilidad, como los List y Slice Patterns. ¡Con cada nueva versión, el Pattern Matching sigue siendo una de las características más poderosas del lenguaje!

sábado, 5 de octubre de 2024

Let assert de Gleam


import gleam/io


pub fn main() {

  let a = unsafely_get_first_element([123])

  io.debug(a)


  let b = unsafely_get_first_element([])

  io.debug(b)

}


pub fn unsafely_get_first_element(items: List(a)) -> a {

  // This will panic if the list is empty.

  // A regular `let` would not permit this partial pattern

  let assert [first, ..] = items

  first

}

El resultado: 

123

Error: Pattern match failed, no pattern matched the value.


let assert es la última forma de bloquear intencionalmente su programa Gleam. Es similar a la palabra clave panic en el sentido de que bloquea cuando el programa ha llegado a un punto al que nunca debería llegar.

let assert es similar a let en el sentido de que es una forma de asignar valores a las variables, pero es diferente en el sentido de que el patrón puede ser parcial. El patrón no necesita coincidir con todos los valores posibles del tipo que se está asignando.

Al igual que panic, esta función debe usarse con moderación y probablemente no debe usarse en absoluto en las bibliotecas.

viernes, 4 de octubre de 2024

Pattern Matching en TypeScript


Pattern Matching es una característica poderosa que permite comparar una estructura de datos con un patrón y ejecutar el código dependiendo de cómo coincidan. Aunque TypeScript no tiene un soporte nativo de pattern matching al estilo de lenguajes como Scala o Haskell, pero se puede simular de manera efectiva utilizando algunas características como los tipos discriminados y el refinamiento de tipos para implementar pattern matching. Estos tipos combinan un campo común discriminante que puede diferenciar uniones de tipos de forma segura.

Veamos un ejemplo:


type Shape = 

  | { kind: 'circle', radius: number }

  | { kind: 'square', sideLength: number }

  | { kind: 'rectangle', width: number, height: number };


function area(shape: Shape): number {

  switch (shape.kind) {

    case 'circle':

      return Math.PI * shape.radius ** 2;

    case 'square':

      return shape.sideLength ** 2;

    case 'rectangle':

      return shape.width * shape.height;

  }

}


const myCircle: Shape = { kind: 'circle', radius: 5 };

console.log(area(myCircle)); // 78.53981633974483


Otra forma de hacer pattern matching es mediante guard clauses, que son condiciones específicas para cada caso. Aquí tienes un ejemplo:


function printNumber(x: number | string): string {

  if (typeof x === 'number') {

    return `Es un número: ${x}`;

  } else if (typeof x === 'string') {

    return `Es una cadena de texto: ${x}`;

  } else {

    return `Valor no soportado`;

  }

}


// Uso

console.log(printNumber(42));   // Es un número: 42

console.log(printNumber('42')); // Es una cadena de texto: 42


TypeScript también permite un estilo de pattern matching mediante desestructuración de objetos y arrays.


type Person = { name: string, age: number };

type Animal = { species: string };


function describe(input: Person | Animal): string {

  if ('name' in input) {

    return `Persona: ${input.name}, Edad: ${input.age}`;

  } else {

    return `Especie: ${input.species}`;

  }

}


// Uso

const person: Person = { name: 'John', age: 30 };

const animal: Animal = { species: 'Dog' };


console.log(describe(person)); // Persona: John, Edad: 30

console.log(describe(animal)); // Especie: Dog


El uso de `switch` puede complementarse con guardias para realizar un matching más fino de patrones, filtrando por condiciones adicionales.


function classifyNumber(x: number): string {

  switch (true) {

    case x < 0:

      return 'Número negativo';

    case x === 0:

      return 'Cero';

    case x > 0:

      return 'Número positivo';

    default:

      return 'Valor desconocido';

  }

}


console.log(classifyNumber(-5));  // Número negativo

console.log(classifyNumber(0));   // Cero

console.log(classifyNumber(10));  // Número positivo


Si bien TypeScript no tiene soporte nativo para el pattern matching al nivel de otros lenguajes funcionales, podemos simularlo utilizando sus características de refinamiento de tipos, tipos discriminados, guard clauses y desestructuración.

Con estos enfoques, puedes aplicar las ideas de pattern matching de forma clara y eficiente en TypeScript. Este tipo de técnica puede mejorar la legibilidad de tu código y hacerlo más fácil de mantener.


jueves, 3 de octubre de 2024

Panic en Gleam


import gleam/io


pub fn main() {

  print_score(10)

  print_score(100_000)

  print_score(-1)

}


pub fn print_score(score: Int) {

  case score {

    score if score > 1000 -> io.println("High score!")

    score if score > 0 -> io.println("Still working on it")

    _ -> panic as "Scores should never be negative!"

  }

}


La palabra clave panic es similar a la palabra clave todo, pero se utiliza para bloquear el programa cuando este ha llegado a un punto al que nunca debería llegar.

¡Esta palabra clave casi nunca debería utilizarse! Puede ser útil en prototipos y scripts iniciales, pero su uso en una biblioteca o aplicación de producción es una señal de que el diseño podría mejorarse. Con tipos bien diseñados, el sistema de tipos se puede utilizar normalmente para hacer que estos estados no válidos sean irrepresentables.

miércoles, 2 de octubre de 2024

Covarianza, Contravarianza e Invarianza en TypeScript


En el contexto de tipos genéricos, la varianza define cómo los subtipos y supertipos de un tipo afectan las relaciones entre otros tipos.

Existen tres tipos principales de varianza:

Covarianza: Un tipo genérico mantiene la relación de subtipos que tiene el tipo con el que trabaja. Es decir, si A es un subtipo de B, entonces Caja<A> será subtipo de Caja<B>.

Contravarianza: Ocurre cuando la relación de subtipos es inversa. Si A es subtipo de B, entonces Caja<B> es subtipo de Caja<A>.

Invarianza: No existe relación entre Caja<A> y Caja<B>, incluso si A es un subtipo de B.

En TypeScript, exiten tipos que son covariantes por defecto. Esto significa que si un tipo genérico tiene un subtipo, esa relación se mantiene con el tipo genérico.

Veamos un ejemplo sencillo para entender esto mejor:


class Animal {

    nombre: string;

    constructor(nombre: string) {

        this.nombre = nombre;

    }

}


class Perro extends Animal {

    ladrar(): void {

        console.log("Guau!");

    }

}


class Gato extends Animal {

    maullar(): void {

        console.log("Miau!");

    }

}


function imprimirAnimales(animales: Animal[]): void {

    animales.forEach(animal => console.log(animal.nombre));

}


const perros: Perro[] = [new Perro("Max"), new Perro("Rex")];

imprimirAnimales(perros); // Esto es válido, ya que Perro es subtipo de Animal


En este ejemplo, Perro[] es subtipo de Animal[], por lo que la función imprimirAnimales puede recibir una lista de perros sin problema. Esto es covarianza.

Contravarianza significa que un tipo más general (supertipo) puede ser pasado donde se espera un tipo más específico (subtipo). En TypeScript, esto es común al trabajar con funciones.


class Carnivoro extends Animal {}

class Herviboro extends Animal {}


function alimentarAnimal(fn: (a: Carnivoro) => void): void {

    const leon = new Carnivoro("León");

    fn(leon);

}


function alimentarCualquierAnimal(a: Animal): void {

    console.log(`Alimentando a un ${a.nombre}`);

}


alimentarAnimal(alimentarCualquierAnimal); // Funciona gracias a la contravarianza

Aquí, la función alimentarCualquierAnimal puede ser utilizada donde se espera una función que trabaje con Carnivoro, ya que Animal es un supertipo de Carnivoro. Esto es contravarianza.

Si un tipo es invariante, no puedes intercambiar subtipos y supertipos, incluso si existe una relación de herencia entre ellos. Este comportamiento es menos común en TypeScript.


Ejemplo de invarianza:


class Caja<T> {

    contenido: T;

    constructor(contenido: T) {

        this.contenido = contenido;

    }

}


const cajaAnimal: Caja<Animal> = new Caja(new Animal("Elefante"));

const cajaPerro: Caja<Perro> = new Caja(new Perro("Max"));


// Esto genera error porque Caja<Perro> no es subtipo de Caja<Animal>

// cajaAnimal = cajaPerro; 


En este caso, Caja<Perro> no es subtipo de Caja<Animal>, aunque Perro sea subtipo de Animal. Esta es la invarianza, donde no hay compatibilidad entre tipos genéricos con diferentes parámetros de tipo.

En TypeScript, no puedes hacer que Caja<Animal> sea compatible con Caja<Perro> directamente debido a la invarianza de los tipos genéricos. Sin embargo, hay formas de aproximar este comportamiento utilizando tipos genéricos más flexibles, como lo son los tipos comodín (similares a los de Java) o utilizando el modificador readonly para hacer la relación covariante.

Se puede hacer que Caja<T> sea covariante para las lecturas si el contenido de la caja solo es accesible de forma de lectura y no de escritura. Esto se logra declarando las propiedades como `readonly`.


class Caja<out T> {

    readonly contenido: T;

    constructor(contenido: T) {

        this.contenido = contenido;

    }

}


const cajaAnimal: Caja<Animal> = new Caja(new Animal("Elefante"));

const cajaPerro: Caja<Perro> = new Caja(new Perro("Max"));

// Como la propiedad es de solo lectura, es covariante

const otraCajaAnimal: Caja<Animal> = cajaPerro; // Funciona


En este caso, al usar readonly, puedes asignar una Caja<Perro> a una Caja<Animal> porque las cajas son covariantes en la lectura. No obstante, ya no podrías modificar el contenido de la caja.

Otra opción es usar la palabra clave extends para indicar que puedes trabajar con cualquier subtipo de Animal.


class Caja<T extends Animal> {

    contenido: T;

    constructor(contenido: T) {

        this.contenido = contenido;

    }

}


function procesarCajaAnimal(caja: Caja<Animal>) {

    console.log(`Animal: ${caja.contenido.nombre}`);

}


const cajaPerro: Caja<Perro> = new Caja(new Perro("Max"));

procesarCajaAnimal(cajaPerro); // Funciona


Aquí estamos diciendo que Caja<T> puede aceptar cualquier tipo que extienda de Animal. Esto permite que una Caja<Perro> sea pasada a una función que espera una Caja<Animal>.

DIPLOMATURA INTRODUCCIÓN A LA PROGRAMACIÓN EN PYTHON


La Diplomatura de Introducción a la Programación con Python es una propuesta diseñada para dar respuesta a una demanda específica de diferentes entornos públicos y privados, así como para proporcionar a los y las participantes una sólida base en programación utilizando el lenguaje Python. Con un enfoque accesible y versátil, la diplomatura busca democratizar el conocimiento tecnológico, preparando a quienes la realicen para enfrentar los desafíos digitales de la actualidad.

La diplomatura consta de 7 módulos con una duración horaria total de 220 horas reloj. Los y las participantes adquirirán habilidades prácticas en programación, aprenderán a desarrollar aplicaciones web simples, trabajarán con bases de datos y se familiarizarán con herramientas esenciales. El enfoque práctico del trayecto formativo se refuerza mediante proyectos reales, permitiendo a los y las estudiantes aplicar de inmediato sus conocimientos en escenarios del mundo real.


Dejo link: https://fcyt.uader.edu.ar/diplomaturapython/

sábado, 28 de septiembre de 2024

Clases Genéricas en TypeScript


Las clases genéricas en TypeScript nos permiten escribir código flexible y reutilizable. En lugar de definir clases específicas para cada tipo de dato, podemos crear clases genéricas que trabajen con múltiples tipos, manteniendo la seguridad de tipos que ofrece TypeScript.

Una clase genérica es aquella que acepta uno o más parámetros de tipo. Estos parámetros de tipo funcionan como marcadores de posición que representan un tipo de dato específico en el momento de la instanciación de la clase.

Veamos un ejemplo: 


class Caja<T> {

    contenido: T;


    constructor(contenido: T) {

        this.contenido = contenido;

    }


    obtenerContenido(): T {

        return this.contenido;

    }

}



En este ejemplo, T es un parámetro de tipo que se utiliza en la clase Caja. No sabemos qué tipo de dato es T hasta que se instancie la clase con un tipo específico.

Puedes instanciar la clase `Caja` con cualquier tipo de dato:


const cajaDeNumero = new Caja<number>(123);

console.log(cajaDeNumero.obtenerContenido()); // 123

const cajaDeTexto = new Caja<string>('Hola');

console.log(cajaDeTexto.obtenerContenido()); // Hola


El tipo `T` se reemplaza con el tipo proporcionado (`number` o `string`), manteniendo el control de tipos y evitando errores durante la compilación.

También es posible utilizar múltiples parámetros de tipo en una clase genérica. Por ejemplo:


class Par<K, V> {

    clave: K;

    valor: V;


    constructor(clave: K, valor: V) {

        this.clave = clave;

        this.valor = valor;

    }


    obtenerClave(): K {

        return this.clave;

    }


    obtenerValor(): V {

        return this.valor;

    }

}


const par = new Par<string, number>('edad', 30);

console.log(par.obtenerClave()); // 'edad'

console.log(par.obtenerValor()); // 30


En este caso, `Par` es una clase genérica que acepta dos parámetros de tipo: `K` para la clave y `V` para el valor. Luego, se instanció con `string` como clave y `number` como valor.

A veces queremos limitar los tipos que un parámetro genérico puede aceptar. Esto se hace usando la palabra clave `extends` para aplicar una restricción al parámetro de tipo.


class LimitarCaja<T extends number | string> {

    contenido: T;


    constructor(contenido: T) {

        this.contenido = contenido;

    }


    mostrarContenido(): void {

        console.log(`Contenido: ${this.contenido}`);

    }

}


const cajaTexto = new LimitarCaja('Hola');

cajaTexto.mostrarContenido(); // Contenido: Hola


const cajaNumero = new LimitarCaja(123);

cajaNumero.mostrarContenido(); // Contenido: 123


Aquí, la clase LimitarCaja solo acepta tipos que sean number o string, lo que añade una restricción al tipo T.

Como ventaja de las clases genericas podemos nombrar: 

  • Reutilización del código: Puedes usar la misma clase para diferentes tipos sin necesidad de duplicar código.
  • Seguridad de tipos: TypeScript asegura que se utilicen los tipos correctos, evitando errores en tiempo de ejecución.
  • Flexibilidad: Permiten que el código sea más flexible, ya que puedes trabajar con cualquier tipo sin perder el control del sistema de tipos.

Las clases genéricas en TypeScript son una herramienta poderosa para crear código flexible y reutilizable. Al permitir que las clases trabajen con múltiples tipos, puedes crear soluciones que se adapten a diferentes escenarios sin sacrificar la seguridad de tipos que hace que TypeScript sea tan útil.