Part of a series of posts explaining programming for the lay-person.
Subroutines are a discrete chunk of a larger routine—a named block of instructions. But sometimes it is nice to have two routines chat back and forth instead of one just including the other.
As an example, suppose we need a pianist for some activity we’re running. To find one we want to come up with the name of someone who can play the piano, then call them up and see if they’re available. If they aren’t, we’ll generate a new person and keep going.
The simplest thing to do is to create a list of all the potential pianists first and then loop through that list. The problem here is that creating a list of all the possible pianists is a gargantuan task; there are millions of people who can play the piano, but we’d really rather not even think about most of them if we can find a willing person within the first few names.
First, note that we never need to use lambdas or coroutines. We can do this using the blocks we already have
The problem here is deciding what the “generate a likely name” subroutine looks like. If we make it random, we might end up getting the same name many times. If we make the subroutine never generate the same name twice then we can’t really re-use it. After finding the pianist for one event, the next event would find the list of names already exhausted.
We can work around these problems be giving the name generator a parameter that tells it what we’ve already seen from it, but writing a subroutine that way is not simple.
Another approach is to make the generator subroutine accept as a parameter a description of what it is to do with each name it generates.
Now we can “find a pianist given ‘call them up and see if they’ll play for us’,” and someone else can “find a pianist given ‘offer them a tutoring job’,” and we can both succeed with the same list of names, without needing to ever look at the entire list.
This programming pattern is called first-class functions, meaning that functions are a “first-class citizen” than can be passed around the same way we’d pass around numbers or strings. It also uses lambda functions, meaning that we describe a function without giving it a name (e.g. just the action, “offer them a tutoring job”, not some named “how to select a piano tutor: …”).
This is a simpler solution than simply using subroutines, but there is something odd about having the list generator be in charge. Generating the list seems like a service provided to the pianist selector, not the other way around. We’d like to mirror our code as closely to what “seems natural” as possible, because that makes it easier to change in the future.
What if we have two subroutines that chat back and forth? You ask me for a name and wait for me to find one; I tell you a name and wait for you to ask me for another one. Back and forth, two routines cooperating to solve the problem.
This is the idea behind coroutines. They come in a lot of flavors, but the simplest allows for one coroutine to yield to the other, and the other to resume the first.
Notice that the end result here leaves the listing of pianists hanging. Once we hire a pianist we never resume listing them. Also, if we run out of pianists the hiring function kind of breaks because we don’t have any sort of “if there are more pianists to list” criterion. Programming languages that have co-routines typically have some sort of rules for handling these conditions.
Coroutines can get a lot more complicated, with routines using several different coroutines and recursive invocations of coroutines. As with much of programming, once you have the blocks you can build almost anything.