1. Overview

In this tutorial, we’ll focus on understanding the yield function in Kotlin through some simple use cases.

2. Basics

The first thing to know about the yield() function is that it is a suspend function that is used in the context of Kotlin Coroutines. When yield() is invoked, it yields the thread (or thread pool) of the current coroutine dispatcher to other coroutines to run if possible.

Secondly, we should understand that yield() and yield(value: T) are two different functions. The latter is used only in the context of sequences, as we’ll explore in the next section.

3. Building Sequences

One of the most common use cases where we can use yield() is to build sequences. So let’s get into action.

3.1. Finite Sequence

Let’s say we want to build a finite sequence of vowels. For such a short sequence, we can use multiple statements that yield a single vowel value. Let’s define vowels() function in the Yield class:

fun vowels() = sequence {
    yield("a")
    yield("e")
    yield("i")
    yield("o")
    yield("u")
}

Now, let’s do an iteration over this sequence using a vowelIterator:

val client = Yield();
val vowelIterator = client.vowels().iterator()
while (vowelIterator.hasNext()) {
    println(vowelIterator.next())
}

Once we run our main function, we should be able to validate the result of the iteration. In this simple scenario, an important thing to note about finite sequences is that we should always use the hasNext() method to check whether there is a next item available in the sequence before invoking the next() method of vowelIterator.

3.2. Infinite Sequence

A more practical use case of using yield() is to build infinite sequences. So, let’s use it to generate terms of a Fibonacci sequence.

To build such a sequence, let’s write a fibonacci() function that uses an infinite loop yielding a single term in each iteration:

fun fibonacci() = sequence {
    var terms = Pair(0, 1)
    while (true) {
        yield(terms.first)
        terms = Pair(terms.second, terms.first + terms.second)
    }
}

Now, let’s verify the first five terms of the sequence by using an iterator for this sequence:

val client = Yield();
val fibonacciIterator = client.fibonacci().iterator();
var count = 5
while (count > 0) {
    println(fibonacciIterator.next())
    count--;
}

For infinite sequences, we can relax the call to the hasNext() method of the iterator as we’re guaranteed to get the next element of the sequence.

4. Cooperative Multitasking

In a cooperative multitasking system, a task would voluntarily yield to allow another job to execute. In this section, we’ll use the yield() function to achieve cooperative multitasking in a simple system.

4.1. Number Printer Orchestrator

Let’s say we want to print all numbers below a specific threshold value.

First, let’s define the current value as 0 using an AtomicInteger and threshold as a constant integer value:

val current = AtomicInteger(0)
val threshold = 10

Now, let’s define a numberPrinter() function to orchestrate the printing of the numbers.

We intend to divide and conquer by defining two different jobs, one for printing even numbers and the other one for printing odd numbers. So, to ensure that we’re waiting until all numbers below the threshold are printed, we’ll define it with the runBlocking CoroutineScope:

fun numberPrinter() = runBlocking {
}

4.2. Odd-Even Number Printer

Now, let’s focus on the part where we’ll delegate the task of printing numbers to two different jobs, namely evenNumberPrinter and oddNumberPrinter.

First, let’s see how we can launch an evenNumberPrinter job:

val evenNumberPrinter = launch {
    while (current.get() < threshold) {
        if (current.get() % 2 == 0) {
            println("$current is even")
            current.incrementAndGet()
        }
        yield()
    }
}

The idea is straightforward; it is printing the current value only when it’s even. Otherwise, it understands that it needs to cooperate with another task that can print the odd values, so it voluntarily yields.

Next, let’s look into the oddNumberPrinter job, which is essentially the same except for the condition where it prints only odd numbers:

val oddNumberPrinter = launch {
    while (current.get() < threshold) {
        if (current.get() % 2 != 0) {
            println("$current is odd")
            current.incrementAndGet()
        }
        yield()
    }
}

Finally, let’s call the numberPrinter() orchestrator to print the numbers:

client.numberPrinter()

As expected, we’re able to see all the numbers below the threshold:

0 is even
1 is odd
2 is even
3 is odd
4 is even
5 is odd
6 is even
7 is odd
8 is even
9 is odd

5. Conclusion

In this tutorial, we took a hands-on approach to understand the yield function in Kotlin by building sequences and achieving cooperative multitasking of jobs.

As always, the source code is available over on GitHub.

Comments are closed on this article!