Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin's Iterable and Sequence look exactly same. Why are two types required?

Both of these interfaces define only one method

public operator fun iterator(): Iterator<T> 

Documentation says Sequence is meant to be lazy. But isn't Iterable lazy too (unless backed by a Collection)?

like image 943
Venkata Raju Avatar asked Feb 25 '16 13:02

Venkata Raju


People also ask

What is the key difference between iterable and Sequence in Kotlin?

The order of operations execution is different as well: Sequence performs all the processing steps one-by-one for every single element. In turn, Iterable completes each step for the whole collection and then proceeds to the next step.

Why use Sequence in Kotlin?

Kotlin sequences have many more processing functions (because they are defined as extension functions) and they are generally easier to use (this is a result of the fact that Kotlin sequences were designed when Java streams was already used — for instance we can collect using toList() instead of collect(Collectors.


2 Answers

The key difference lies in the semantics and the implementation of the stdlib extension functions for Iterable<T> and Sequence<T>.

  • For Sequence<T>, the extension functions perform lazily where possible, similarly to Java Streams intermediate operations. For example, Sequence<T>.map { ... } returns another Sequence<R> and does not actually process the items until a terminal operation like toList or fold is called.

    Consider this code:

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

    It prints:

    before sum 1 2 

    Sequence<T> is intended for lazy usage and efficient pipelining when you want to reduce the work done in terminal operations as much as possible, same to Java Streams. However, laziness introduces some overhead, which is undesirable for common simple transformations of smaller collections and makes them less performant.

    In general, there is no good way to determine when it is needed, so in Kotlin stdlib laziness is made explicit and extracted to the Sequence<T> interface to avoid using it on all the Iterables by default.

  • For Iterable<T>, on contrary, the extension functions with intermediate operation semantics work eagerly, process the items right away and return another Iterable. For example, Iterable<T>.map { ... } returns a List<R> with the mapping results in it.

    The equivalent code for Iterable:

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

    This prints out:

    1 2 before sum 

    As said above, Iterable<T> is non-lazy by default, and this solution shows itself well: in most cases it has good locality of reference thus taking advantage of CPU cache, prediction, prefetching etc. so that even multiple copying of a collection still works good enough and performs better in simple cases with small collections.

    If you need more control over the evaluation pipeline, there is an explicit conversion to a lazy sequence with Iterable<T>.asSequence() function.

like image 199
hotkey Avatar answered Oct 15 '22 22:10

hotkey


Completing hotkey's answer:

It is important to notice how Sequence and Iterable iterates throughout your elements:

Sequence example:

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

Log result:

filter - Map - Each; filter - Map - Each

Iterable example:

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

filter - filter - Map - Map - Each - Each

like image 42
Leandro Borges Ferreira Avatar answered Oct 16 '22 00:10

Leandro Borges Ferreira