
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:
- Streaming de datos: Procesar archivos grandes línea por línea
- Paginación: Cargar datos de una API página por página
- Simulaciones: Generar secuencias de eventos o estados
- 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*
:
- Composición: Permite combinar generadores de manera limpia
- Reutilización: Puedes reutilizar lógica de generadores existentes
- Legibilidad: Código más claro y modular
- 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

¿Por qué deberías usar Cloudflare workers?
Checa este otro Post
