
- async functions (also called async/await);
- generator functions (functions in which subsequent values are yielded).
async can be used in Kotlin, but this will be explained in detail in the Starting coroutines chapter. Instead of generators, Kotlin provides a sequence builder - a function used to create a sequence[^102_1].List or Set), but it is evaluated lazily, meaning the next element is always calculated on demand, when it is needed. As a result, sequences:- do the minimal number of required operations;
- can be infinite;
- are more memory-efficient[^102_2].
sequence function. Inside its lambda expression, we can call the yield function to produce the next elements of this sequence.val seq = sequence { yield(1) yield(2) yield(3) } fun main() { for (num in seq) { print(num) } // 123 }
Thesequencefunction here is a small DSL. Its argument is a lambda expression with a receiver (suspend SequenceScope<T>.() -> Unit). Inside it, the receiverthisrefers to an object of typeSequenceScope<T>.SequenceScope<T>has functions likeyield. When we callyield(1), it is equivalent to callingthis.yield(1)becausethiscan be used implicitly. If this is your first contact with lambda expressions with receivers, I recommend starting from learning about them and about DSL creation, as they are used intensively in Kotlin Coroutines. I explained DSL creation in detail in the book Functional Kotlin.
import kotlin.* //sampleStart val seq = sequence { println("Generating first") yield(1) println("Generating second") yield(2) println("Generating third") yield(3) println("Done") } fun main() { for (num in seq) { println("The next number is $num") } } // Generating first // The next number is 1 // Generating second // The next number is 2 // Generating third // The next number is 3 // Done //sampleEnd
main and the sequence generator.
When we ask for the next value in the sequence, we resume in the builder straight after the previous
yield.import kotlin.* //sampleStart val seq = sequence { println("Generating first") yield(1) println("Generating second") yield(2) println("Generating third") yield(3) println("Done") } fun main() { val iterator = seq.iterator() println("Starting") val first = iterator.next() println("First: $first") val second = iterator.next() println("Second: $second") // ... } // Prints: // Starting // Generating first // First: 1 // Generating second // Second: 2 //sampleEnd
import java.math.BigInteger //sampleStart val fibonacci: Sequence<BigInteger> = sequence { var first = 0.toBigInteger() var second = 1.toBigInteger() while (true) { yield(first) val temp = first first += second second = temp } } fun main() { print(fibonacci.take(10).toList()) } // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] //sampleEnd
fun randomNumbers( seed: Long = System.currentTimeMillis() ): Sequence<Int> = sequence { val random = Random(seed) while (true) { yield(random.nextInt()) } } fun randomUniqueStrings( length: Int, seed: Long = System.currentTimeMillis() ): Sequence<String> = sequence { val random = Random(seed) val charPool = ('a'..'z') + ('A'..'Z') + ('0'..'9') while (true) { val randomString = (1..length) .map { i -> random.nextInt(charPool.size) } .map(charPool::get) .joinToString("") yield(randomString) } }.distinct()
fun allUsersFlow( api: UserApi ): Flow<User> = flow { var page = 0 do { val users = api.takePage(page++) // suspending emitAll(users) } while (!users.isNullOrEmpty()) }
[^102_2]: See item Prefer Sequence for big collections with more than one processing step in the Effective Kotlin.
[^102_3]: And it cannot be, since
SequenceScope is annotated with RestrictsSuspension, which prevents the suspend function being called unless its receiver is SequenceScope.