In this part of the guide, we will explain what coroutines are, how to create them and use some basic coroutine operations. If you did not setup a project that uses Scala Coroutines, please go through the Setup and Dependencies. Otherwise, please proceed with the next section.

Coroutines 101

A coroutine is a programming construct that can suspend its execution and resume execution later. Subroutines (i.e. functions, procedures or methods) are well known and available in most programming languages. For the purposes of this guide, we will jointly refer to functions, procedures, methods and lambdas as subroutines, despite their subtle differences. When a subroutine is called, its execution begins at the start of the subroutine definition, and finishes when the end of the subroutine is reached. A coroutine is more powerful – it can pause execution at any point between the start and the end of the coroutine definition. In this sense, coroutines generalize subroutines.

To better understand how coroutines work, we contrast them to lambdas from functional programming. Consider the following definition of an identity lambda:

val id = (x: Int) => x

We call (i.e. invoke) the identity lambda id as follows:

id(7)

When id is invoked, the following sequence of events occurs. First, the runtime allocates a location on the program stack to hold the parameter x. Then, the value of x is copied to this location.

The id body then simply returns the value x back to the caller by copying it back to the appropriate location. After that, the invocation of id (i.e. an instance of the subroutine id) completes.

Above, the caller of the id lambda (e.g. the main program) must suspend execution until id completes. After id returns control to the caller, the instance of the subroutine id no longer exists, so it does not make sense to be able to refer to it. An invocation (i.e. an instance of a subroutine) is therefore not a first-class object.

By contrast, a coroutine invocation can suspend its execution before it completes, and later be resumed. For this reason, it makes sense to represent the coroutine invocation (i.e. a coroutine instance) as a first-class object that can be passed to and returned from functions, whose execution state can be retrieved, and which can be resumed when required.

Let’s see the simplest possible coroutine definition – an identity coroutine, which just returns the argument that is passed to it. Its definition is similar to that of an identity lambda, the only difference being the enclosing coroutine block:

val id = coroutine { (x: Int) => x }

Here, the difference between a coroutine definition and a coroutine instance becomes more prominent. We cannot create and get a hold of a coroutine instance simultaneously with obtaining its return value, so starting a coroutine is a two step process. First, a coroutine is instantiated with the call keyword, which returns a coroutine instance object. Second, a coroutine instance is started by calling its resume method:

val c = call(id(7))
c.resume
assert(c.result == 7)

Above, we first use the call keyword to create an id coroutine instance called c. This coroutine instance object c is created in a suspended state, and is resumed by calling c.resume. To obtain the resulting value from the coroutine, we call c.result. The assert statement is used here to verify that the return value is really what we expect. Importantly, the same coroutine definition can be reused to create many coroutine instances, much like a lambda can be invoked more than once.

A coroutine is defined once by passing a lambda literal to the special coroutine block. A coroutine is invoked by passing an invocation expression to the call keyword, which returns a fresh coroutine instance.

Note that we are explicit about invoking the coroutine id with the call construct, to signify that this creates a fresh coroutine instance. Also, note that we use the term invocation and instance interchangeably to refer to starting a coroutine.

The complete, standalone example snippet is shown below:


Although this particular coroutine id is semantically equivalent to the prior definition of the identity lambda id, its execution is slightly different. A coroutine instance does not execute on the same program stack as the caller. Instead, it maintains a separate highly optimized stack, used to store its execution state,

The id coroutine is so simple that it does not even suspend execution after it starts. The only point when it is suspended is immediately after its instance is created. Therefore, this particular coroutine does not even require a state stack. We will study more complex coroutines next.

Yielding with coroutines

To understand why a separate stack is required, we will study a more complex coroutine that takes a string and yields counts of different vowels in that string. To yield control and a value back to the caller, coroutines use the special yieldval statement. Consider the following example.


Above, we define a coroutine called vowelcounts, which takes a String parameter. This coroutine then calls the count method on String a total of five times, each time for a different vowel character. It uses the yieldval statement five times to suspend execution and yield the count to the main program. Each time yieldval is called, the coroutine execution is paused, and control is returned to the caller. The caller then calls resume on the coroutine instance c to resume the coroutine.

It is illegal to call resume on an already completed coroutine instance – doing this raises an exception.

Summary

To summarize, there are four important coroutine operations you need to remember:

  • coroutine – used to define a coroutine, e.g. coroutine { (x: Int) => x }
  • call – used to invoke a coroutine, i.e. to start a coroutine instance
  • resume – used to resume a paused coroutine
  • yieldval – used only inside a coroutine to suspend execution and yield a value to the caller

And that’s it – you already know everything you need to get productive with coroutines. You can now start coding, or continue to the next section, where we learn about the coroutine data types.