cover

Generadores en Effect y el keyword yield*


Generadores en Effect

Primero: ¿Qué son los generadores en JS/TS?

Un generador es una función especial en JavaScript/TypeScript que puede pausar su ejecución y reanudarla más tarde, permitiendo producir una secuencia de valores de manera controlada. ✅

A diferencia de las funciones normales que se ejecutan completamente de una vez, los generadores utilizan la palabra clave yield para "entregar" valores uno por uno y mantener su estado interno entre llamadas. 📞

Esto los hace ideales para manejar secuencias grandes de datos, operaciones asíncronas complejas, o cuando necesitas procesar información de manera incremental sin consumir toda la memoria de una vez. Los generadores son especialmente útiles para implementar iteradores personalizados, manejar streams de datos, y crear flujos de control complejos de manera más legible y eficiente. 🧠

¿Qué son los generadores en Effect?

Los generadores en Effect son una forma de crear secuencias de valores de manera lazy (perezosa) y composable. Son similares a los generadores nativos de JavaScript/TypeScript, pero están integrados con el sistema de tipos y efectos de Effect.

Características principales:

1. Lazy Evaluation (Evaluación Perezosa)

Los generadores no ejecutan hasta que se consuman, lo que permite crear secuencias infinitas de manera eficiente.

2. Composición Funcional

Se pueden combinar y transformar usando operadores funcionales como map, filter, flatMap, etc.

3. Integración con Effect

Los generadores pueden producir efectos (como operaciones de I/O) y manejar errores de manera funcional.

Ejemplos básicos:

Generador simple:

import { Effect, Generator } from "@effect/core" // Generador que produce números del 1 al 5 const numbers = Generator.fromIterable([1, 2, 3, 4, 5]) // Generador infinito const infiniteNumbers = Generator.unfold(0, n => [n, n + 1])

Transformaciones:

// Mapear valores const doubled = numbers.pipe( Generator.map(n => n * 2) ) // Filtrar valores const evens = numbers.pipe( Generator.filter(n => n % 2 === 0) ) // Combinar generadores const combined = Generator.zip(numbers, doubled)

Generadores con efectos:

// Generador que produce efectos const effectGenerator = Generator.unfoldEffect( Effect.succeed(0), n => Effect.succeed([n, n + 1]).pipe( Effect.tap(n => Effect.log(`Generated: ${n}`)) ) )

Casos de uso comunes:

  1. Streaming de datos: Procesar archivos grandes línea por línea
  2. Paginación: Cargar datos de una API página por página
  3. Simulaciones: Generar secuencias de eventos o estados
  4. Transformaciones de datos: Pipeline de procesamiento de datos

Ventajas sobre generadores nativos:

  • Type Safety: Mejor inferencia de tipos
  • Composición: Más fácil combinar y transformar
  • Error Handling: Manejo de errores integrado con Effect
  • Testing: Más fácil de testear con el sistema de Effect
  • Performance: Optimizaciones automáticas

yield*

¿Qué es yield*?

yield* es una expresión que permite delegar la generación de valores a otro generador o iterable. Es como pasar el control de la generación a otro generador.

Sintaxis básica:

function* generator1() { yield 1 yield 2 } function* generator2() { yield 'a' yield* generator1() // Delega a generator1 yield 'b' } // Resultado: 'a', 1, 2, 'b'

Ejemplos prácticos:

1. Composición de generadores

function* numbers() { yield 1 yield 2 yield 3 } function* letters() { yield 'a' yield 'b' } function* combined() { yield* numbers() // Produce: 1, 2, 3 yield* letters() // Produce: 'a', 'b' } // Resultado: 1, 2, 3, 'a', 'b'

2. Con arrays y otros iterables

function* flatten() { yield* [1, 2, 3] // Array yield* "hello" // String (iterable) yield* new Set([4, 5]) // Set } // Resultado: 1, 2, 3, 'h', 'e', 'l', 'l', 'o', 4, 5

3. Generadores recursivos

function* treeTraversal(node) { if (!node) return yield node.value yield* treeTraversal(node.left) yield* treeTraversal(node.right) }

En Effect:

En Effect, yield* se usa de manera similar pero con generadores que pueden producir efectos:

import { Effect, Generator } from "@effect/core" function* effectGenerator() { yield* Effect.succeed(1) yield* Effect.succeed(2) yield* Effect.succeed(3) } // O con generadores de Effect function* combinedEffects() { yield* Generator.fromIterable([1, 2, 3]) yield* Generator.unfold(0, n => [n, n + 1]).pipe( Generator.take(3) ) }

Diferencias importantes:

yield vs yield*:

function* example() { // yield: produce el valor tal como está yield [1, 2, 3] // Produce: [1, 2, 3] (un array) // yield*: itera sobre el valor yield* [1, 2, 3] // Produce: 1, 2, 3 (tres valores separados) }

Casos de uso avanzados:

1. Pipeline de procesamiento

function* processData() { const rawData = yield* fetchData() const processed = yield* transformData(rawData) yield* saveData(processed) }

2. Composición de efectos

function* userWorkflow() { const user = yield* getUser() const permissions = yield* getPermissions(user.id) yield* validateAccess(permissions) return yield* performAction(user, permissions) }

Ventajas de yield*:

  1. Composición: Permite combinar generadores de manera limpia
  2. Reutilización: Puedes reutilizar lógica de generadores existentes
  3. Legibilidad: Código más claro y modular
  4. Performance: Eficiente para iterables grandes

Consideraciones:

  • yield* consume el generador delegado completamente
  • Si el generador delegado produce efectos, estos se ejecutan en secuencia
  • Es útil para evitar anidamiento excesivo de generadores
meta cover

¿Por qué deberías usar Cloudflare workers?

Checa este otro Post

meta cover

Cómo resolver el error: "./prisma/client/index-browser" is not a valid package al hacer build

Checa este otro Post

¡Nuevo curso!

Animaciones web con React + Motion 🧙🏻