Un vistazo al álgebra de sistemas de tipos
Tags: rust, typescript
Published: 2024-04-04 18:24:00

---
title: Un vistazo al álgebra de sistemas de tipos
published: true
description: Cómo escribir código más correcto usando un poco de álgebra de tipos en Rust y TypeScript.
tags: rust, typescript
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5j7jc12s8vv32y4xycwv.jpg
published_at: 2024-04-04 18:24 +0000
---

_Esta nota es una traducción de [la nota original](https://dev.to/alex_escalante/a-glimpse-into-the-algebra-of-type-systems-2gee) que publiqué más temprano. No estoy seguro si hoy en día tiene sentido seguir traduciendo, díganmelo en los comentarios, por favor…_

---

Acabo de ver este video muy interesante sobre cómo escribir programas que inherentemente [hacen que el estado no válido sea irrepresentable](https://youtu.be/z-0-bbc80JM?si=GMAGB35X2OZqxnGH). Este podría ser un concepto muy importante de entender, especialmente si todavía estás aprendiendo o tu experiencia está basada en lenguajes que no cuentan con un sistema estricto de tipos.

Un "sistema de tipos algebraicos" en programación de computadoras se refiere a un sistema que permite la construcción de tipos complejos a partir de tipos simples mediante operaciones algebraicas. Estas operaciones suelen incluir suma (unión) y producto (combinación) de tipos. Los lenguajes con tipos algebraicos presentan oportunidades únicas para modelar datos imponiendo restricciones e invariantes sobre sus tipos, haciendo así que los programas sean más robustos y más fáciles de entender. 

Rust y TypeScript son dos lenguajes que utilizan sistemas de tipos algebraicos de diferentes maneras.

### Suma de tipos (_union types_)

Los  _union types_  permiten que un valor sea uno de varios tipos diferentes. Esto es análogo a una operación "or" en álgebra booleana. En programación, esto es útil para modelar situaciones en las que un valor puede provenir de tipos dispares y el programa necesita manejar cada tipo de manera diferente.

- **Rust:** Rust usa `enum` para definir tipos de suma. Una enumeración en Rust puede tener variantes y cada variante puede, opcionalmente, contener datos de diferentes tipos. Esto permite a Rust modelar estructuras y patrones de datos complejos, como máquinas de estado o valores opcionales (por ejemplo, el tipo `Option<T>`) de forma segura.
- **TypeScript:** TypeScript tiene tipos de unión que se indican mediante el operador `|`. Esto permite que una variable contenga un valor de uno de varios tipos, lo que permite al programador escribir código flexible manteniendo la seguridad de tipos. Por ejemplo, se podría definir una variable para contener un `string` o un `number`.

### Producto de tipos (_structs_)

Un “tipos producto” permite la combinación de varios valores en un valor compuesto, donde la parte "producto" surge de la idea de multiplicar las posibilidades de cada tipo de componente para obtener las posibilidades totales del tipo compuesto. Esto es útil para agrupar datos relacionados.

- **Rust:** Rust tiene `struct` que se utiliza para crear tipos de datos complejos combinando valores de múltiples tipos. Cada campo de una estructura puede tener un tipo diferente, lo que permite la construcción de estructuras de datos ricas y tipificadas.
- **TypeScript:** TypeScript utiliza interfaces y clases como su medio principal para crear tipos compuestos. Las interfaces en TypeScript se utilizan para definir la forma que deben adoptar los objetos, incluidos los tipos de sus propiedades y métodos. Las clases pueden implementar interfaces y proporcionar implementaciones concretas.

### Tipos de datos algebraicos en programación

Los tipos de datos algebraicos (ADT) en lenguajes de programación como Rust y TypeScript mejoran la seguridad y la expresividad de los tipos. Permiten a los desarrolladores modelar datos de una manera que hace que los estados no válidos sean irrepresentables, lo que reduce significativamente los errores de tiempo de ejecución. Por ejemplo, en Rust, el compilador verifica que las expresiones coincidentes sean exhaustivas, asegurando que se manejen todas las variantes posibles de una enumeración. En TypeScript, se pueden utilizar tipos de unión y protecciones de tipos para garantizar que el código maneje correctamente diferentes tipos en tiempo de ejecución.

Tanto Rust como TypeScript aprovechan sus sistemas de tipos para brindar garantías en tiempo de compilación sobre el comportamiento del código, lo que hace que el proceso de desarrollo de software sea más confiable y eficiente. Si bien los detalles de cómo implementan estas características difieren, el principio subyacente de usar tipos para imponer restricciones y modelar datos complejos es una fortaleza compartida de ambos lenguajes.

Veamos un ejemplo que muestra el uso de los tipos de unión de TypeScript junto con las protecciones de tipos para manejar diferentes tipos en tiempo de ejecución de forma segura.

### Ejemplo: Distinguiendo entre diferentes figuras

Imagine que está trabajando en una aplicación de gráficos que puede representar diferentes tipos de figuras. Cada figura (como círculo, cuadrado) tiene su propio conjunto de propiedades. Queremos escribir una función que tome una figura e imprima su área, usando _union types_ para las figuras y _type guards_ para distinguirlas en tiempo de ejecución.

#### Paso 1: Definir tipos de formas con unión

Primero, definimos las figuras que estamos manejando: `Círculo` y `Cuadrado`. Luego definimos un tipo `Shape` que es una unión de estos tipos.

```typescript
type Circle = {
    kind: 'circle';
    radius: number;
};

type Square = {
    kind: 'square';
    sideLength: number;
};

// Union type for any shape
type Shape = Circle | Square;
```

#### Paso 2: Implementar _type guards_

TypeScript nos permite usar “protectores de tipo” definidos por el usuario para verificar el tipo de un _union type_ en tiempo de ejecución. Un _type guard_ es una función que devuelve un valor booleano y tiene un predicado de tipo como tipo de retorno (`arg is Type`).

Para nuestras formas, podemos usar la propiedad `kind` para distinguirlas:

```typescript
function isCircle(shape: Shape): shape is Circle {
    return shape.kind === 'circle';
}

function isSquare(shape: Shape): shape is Square {
    return shape.kind === 'square';
}
```

#### Paso 3: Escribir una función para manejar figuras

Ahora, escribimos una función que toma una  figura e imprime su área. Usaremos los _type guards_ para acceder de forma segura a las propiedades específicas de cada tipo.

```typescript
function printArea(shape: Shape) {
    if (isCircle(shape)) {
        // TypeScript knows `shape` is a Circle here
        const area = Math.PI * shape.radius ** 2;
        console.log(`Area of the circle: ${area}`);
    } else if (isSquare(shape)) {
        // TypeScript knows `shape` is a Square here
        const area = shape.sideLength ** 2;
        console.log(`Area of the square: ${area}`);
    }
}

// Example usage
const circle: Circle = { kind: 'circle', radius: 2 };
const square: Square = { kind: 'square', sideLength: 3 };

printArea(circle); // Area of the circle: 12.566370614359172
printArea(square); // Area of the square: 9
```

Este ejemplo muestra cómo los _union types_ y las _type guards_ de TypeScript se pueden usar para escribir código seguro con verificación de tipos que maneje múltiples tipos en tiempo de ejecución. Al verificar la propiedad `kind`, podemos decirle a TypeScript con qué tipo estamos tratando dentro de los bloques `if`, lo que nos permite acceder a las propiedades únicas de cada tipo sin correr el riesgo de un error de tiempo de ejecución.