DMarlon Studio

Lección 19 · 19 de 20

Iterables y generadores · Dart

Genere secuencias perezosas en Dart con sync*, yield y yield*: trabaje con series infinitas y calcule solo lo que de verdad se pide.

Dart · Iterables y generadores en DMarlon Studio: el código del ejemplo y su salida en la terminal

Una función **sync* devuelve un `Iterable` perezoso: su cuerpo no se ejecuta al llamarla, sino a medida que alguien pide valores, y cada yield** entrega uno y deja la función en pausa hasta el siguiente. invoiceNumbers es un bucle sin fin, y no pasa nada: skip(41).take(3) pide justo los números 42 a 44 y la función no avanza más.

readPrices escribe una línea cada vez que entrega un precio, para ver cuándo trabaja. La cadena readPrices().where(...).map(...) todavía no lee nada: solo describe la consulta, y por eso Query built, nothing read yet sale antes que cualquier reading. Los métodos de Iterable como where, map, skip o take también son perezosos.

Al pedir expensive.first empieza el trabajo: se leen 120, 45 y 990, y al llegar al primero que supera 500 se le suma el IVA con aritmética entera (* 121 ~/ 100) y todo se detiene. Los precios 300 y 1500 no se leen nunca, algo que importa con miles de registros o con un origen de datos costoso. Ojo: si recorre expensive dos veces, la función se ejecuta dos veces; guarde el resultado con toList() si lo va a reutilizar.

yield*** entrega todos los valores de otro iterable. chunks lo usa para ser recursiva: entrega el primer bloque de size elementos y delega el resto en otra llamada a sí misma. Es una forma cómoda de paginar una lista.

Código · main.dart

// sync* returns a lazy Iterable: the body runs only as values are requested.Iterable<String> invoiceNumbers(String prefix) sync* {  for (var n = 1;; n++) {    yield '$prefix-${n.toString().padLeft(4, '0')}'; // infinite, and that's fine  }}Iterable<int> readPrices() sync* {  for (final price in [120, 45, 990, 300, 1500]) {    print('  reading $price');    yield price;  }}// yield* hands over every value of another iterable.Iterable<List<T>> chunks<T>(List<T> items, int size) sync* {  if (items.length <= size) {    yield items;    return;  }  yield items.sublist(0, size);  yield* chunks(items.sublist(size), size);}void main() {  print(invoiceNumbers('INV').skip(41).take(3).toList());  final expensive = readPrices().where((p) => p > 500).map((p) => p * 121 ~/ 100);  print('Query built, nothing read yet');  print('First expensive with VAT: ${expensive.first}'); // stops at 990  print('Pages: ${chunks(['ana', 'luis', 'eva', 'tom', 'iker'], 2).toList()}');}

Salida · dart run main.dart

[INV-0042, INV-0043, INV-0044]
Query built, nothing read yet
  reading 120
  reading 45
  reading 990
First expensive with VAT: 1197
Pages: [[ana, luis], [eva, tom], [iker]]

Para recordar

  • sync* devuelve un Iterable que se calcula al recorrerlo.
  • yield entrega un valor; yield*, todos los de otro iterable.
  • Un generador puede ser infinito si solo se piden unos cuantos valores.
  • where, map, skip y take no hacen nada hasta que se recorre el resultado.
  • Cada recorrido vuelve a ejecutar el generador; use toList() para guardarlo.

Ejercicio

Escriba un generador infinito fibonacci() con sync* y muestre el primer número de Fibonacci mayor que 1000 con firstWhere.