Translate

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/