kotlin iterable lazy-sequences

Iterable y Secuencia de Kotlin se ven exactamente iguales. ¿Por qué se requieren dos tipos?



lazy-sequences (2)

Ambas interfaces definen solo un método

public operator fun iterator(): Iterator<T>

La documentación dice que la Sequence debe ser perezosa. ¿Pero no es Iterable perezoso también (a menos que esté respaldado por una Collection )?


Completando la respuesta de la tecla de acceso rápido:

Es importante notar cómo Secuencia e Iterable itera a través de sus elementos:

Ejemplo de secuencia:

list.asSequence() .filter { field -> Log.d("Filter", "filter") field.value > 0 }.map { Log.d("Map", "Map") }.forEach { Log.d("Each", "Each") }

Resultado del registro:

filtro - Mapa - Cada uno; filtro - Mapa - Cada

Ejemplo iterable:

list.filter { field -> Log.d("Filter", "filter") field.value > 0 }.map { Log.d("Map", "Map") }.forEach { Log.d("Each", "Each") }

filtro - filtro - Mapa - Mapa - Cada - Cada


La diferencia clave radica en la semántica y la implementación de las funciones de extensión stdlib para Iterable<T> y Sequence<T> .

  • Para Sequence<T> , las funciones de extensión funcionan de manera perezosa cuando sea posible, de manera similar a las operaciones intermedias de Java Streams. Por ejemplo, Sequence<T>.map { ... } devuelve otra Sequence<R> y en realidad no procesa los elementos hasta que se llama a una operación de terminal como toList o fold .

    Considera este código:

    val seq = sequenceOf(1, 2) val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate print("before sum ") val sum = seqMapped.sum() // terminal

    Imprime:

    before sum 1 2

    Sequence<T> está diseñada para un uso diferido y una canalización eficiente cuando se desea reducir el trabajo realizado en operaciones de terminal tanto como sea posible, lo mismo que para Java Streams. Sin embargo, la pereza introduce cierta sobrecarga, lo que no es deseable para las transformaciones simples comunes de colecciones más pequeñas y las hace menos productivas.

    En general, no hay una buena manera de determinar cuándo es necesario, por lo que en Kotlin stdlib la pereza se hace explícita y se extrae a la interfaz Sequence<T> para evitar su uso en todos los Iterable por defecto.

  • Para Iterable<T> , por el contrario, las funciones de extensión con semántica de operación intermedia funcionan con entusiasmo, procesan los elementos de inmediato y devuelven otro Iterable . Por ejemplo, Iterable<T>.map { ... } devuelve una List<R> con los resultados del mapeo.

    El código equivalente para Iterable:

    val lst = listOf(1, 2) val lstMapped: List<Int> = lst.map { print("$it "); it * it } print("before sum ") val sum = lstMapped.sum()

    Esto imprime:

    1 2 before sum

    Como se dijo anteriormente, Iterable<T> no es perezoso por defecto, y esta solución se muestra bien: en la mayoría de los casos tiene una buena localidad de referencia , aprovechando el caché de la CPU, la predicción, la captación previa, etc., de modo que incluso la copia múltiple de un La colección todavía funciona lo suficientemente bien y funciona mejor en casos simples con colecciones pequeñas.

    Si necesita más control sobre la canalización de evaluación, hay una conversión explícita a una secuencia diferida con la función Iterable<T>.asSequence() .