cover

Generadores en Effect y el keyword yield*


Escuchar este post

Selecciona una voz y genera audio para escuchar este post

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é ya no deberías llamarle JavaScript?

Checa este otro Post

meta cover

Te explico el método forEach

Checa este otro Post

¡Nuevo curso!

Animaciones web con React + Motion 🧙🏻